From 36c4a19f6be9d17a8b95d090b90dfe1a515062ef Mon Sep 17 00:00:00 2001 From: Antonio Dell'Annunziata Date: Fri, 28 Feb 2025 15:43:23 +0100 Subject: [PATCH] Finish dark cut prototype --- .gitignore | 5 +- bat_dropoff.gd | 4 + bat_extraction_bat.gd | 36 +++ cut_sequence.gd | 6 +- export_presets.cfg | 289 +++++++++++++++++++++++ feedback_canvas_modulate.gd | 5 + grab_step.gd | 41 ++++ operation.gd | 24 +- pickup_area.gd | 50 ++++ pickup_area.tscn | 11 + pliers.gd | 18 ++ pliers.png | Bin 0 -> 2299 bytes pliers.png.import | 34 +++ project.godot | 2 + prototype_dark_cut.tscn | 194 ++++++++++++--- scalpel_pickup_area_collision_shape.tres | 5 + test_scalpel.gd | 44 +--- 17 files changed, 689 insertions(+), 79 deletions(-) create mode 100644 bat_dropoff.gd create mode 100644 bat_extraction_bat.gd create mode 100644 export_presets.cfg create mode 100644 grab_step.gd create mode 100644 pickup_area.gd create mode 100644 pickup_area.tscn create mode 100644 pliers.gd create mode 100644 pliers.png create mode 100644 pliers.png.import create mode 100644 scalpel_pickup_area_collision_shape.tres diff --git a/.gitignore b/.gitignore index ec9bd70..5b22aae 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,7 @@ export_credentials.cfg # Mono-specific ignores .mono/ data_*/ -mono_crash.*.json \ No newline at end of file +mono_crash.*.json + +# Project specific ignores +out/ diff --git a/bat_dropoff.gd b/bat_dropoff.gd new file mode 100644 index 0000000..2e329ce --- /dev/null +++ b/bat_dropoff.gd @@ -0,0 +1,4 @@ +class_name BatDropoff +extends Sprite2D + +@onready var dropoff_area: Area2D = $Area2D diff --git a/bat_extraction_bat.gd b/bat_extraction_bat.gd new file mode 100644 index 0000000..31c8e06 --- /dev/null +++ b/bat_extraction_bat.gd @@ -0,0 +1,36 @@ +class_name BatExtractionBat +extends Sprite2D + + +@export var speed: float = 0.2 + +@onready var grab_area: Area2D = $BatArea + +var path_follow: PathFollow2D +var is_grabbed = false +var is_dropped = false +var pliers: Pliers + +func _ready(): + path_follow = get_parent() as PathFollow2D + pliers = get_tree().get_first_node_in_group("grabber") as Pliers + +func _physics_process(delta): + if is_dropped: return + + if is_grabbed: + global_position = pliers.grab_area.global_position + else: + path_follow.progress_ratio += delta * speed + + +func grab_bat(): + is_grabbed = true + grab_area.set_deferred("monitoring", false) + grab_area.set_deferred("monitorable", false) + path_follow.progress_ratio = 0.0 + position = Vector2.ZERO + top_level = true + +func drop_bat(): + is_dropped = true diff --git a/cut_sequence.gd b/cut_sequence.gd index f5cb364..341e555 100644 --- a/cut_sequence.gd +++ b/cut_sequence.gd @@ -4,6 +4,8 @@ extends Node2D signal sequence_failed signal sequence_succeeded +@export var is_skipped = false + var cut_sequence_points: Array[CutSequencePoint] = [] func _ready(): _build_sequence() @@ -23,12 +25,12 @@ func _trigger_point_cut(point: CutSequencePoint): if cut_indices.is_empty(): is_cut_valid = cut_index == 0 or cut_index == cut_sequence_points.size() - 1 if not is_cut_valid: - print("Failed cut: Expected %i or %i as first point, but got %i." % [0, cut_sequence_points.size() - 1, cut_index]) + print("Failed cut: Expected %s or %s as first point, but got %s." % [0, cut_sequence_points.size() - 1, cut_index]) else: var direction = -1 if cut_indices[0] == 0 else 1 is_cut_valid = cut_index + direction == cut_indices[-1] # Expect the next index if not is_cut_valid: - print("Failed cut: Expected %i as next point, but got %i." % [cut_indices[-1] - direction, cut_index]) + print("Failed cut: Expected %s as next point, but got %s." % [cut_indices[-1] - direction, cut_index]) if not is_cut_valid: _fail_sequence() diff --git a/export_presets.cfg b/export_presets.cfg new file mode 100644 index 0000000..0bbdf55 --- /dev/null +++ b/export_presets.cfg @@ -0,0 +1,289 @@ +[preset.0] + +name="necra_prototype_macOS" +platform="macOS" +runnable=true +advanced_options=false +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="out/necra_prototype_macOS.zip" +encryption_include_filters="" +encryption_exclude_filters="" +encrypt_pck=false +encrypt_directory=false +script_export_mode=2 + +[preset.0.options] + +export/distribution_type=1 +binary_format/architecture="universal" +custom_template/debug="" +custom_template/release="" +debug/export_console_wrapper=1 +application/icon="" +application/icon_interpolation=4 +application/bundle_identifier="dev.antonio-da.necra-prototype" +application/signature="" +application/app_category="Games" +application/short_version="" +application/version="" +application/copyright="" +application/copyright_localized={} +application/min_macos_version="10.12" +application/export_angle=0 +display/high_res=true +application/additional_plist_content="" +xcode/platform_build="14C18" +xcode/sdk_version="13.1" +xcode/sdk_build="22C55" +xcode/sdk_name="macosx13.1" +xcode/xcode_version="1420" +xcode/xcode_build="14C18" +codesign/codesign=1 +codesign/installer_identity="" +codesign/apple_team_id="" +codesign/identity="" +codesign/entitlements/custom_file="" +codesign/entitlements/allow_jit_code_execution=false +codesign/entitlements/allow_unsigned_executable_memory=false +codesign/entitlements/allow_dyld_environment_variables=false +codesign/entitlements/disable_library_validation=false +codesign/entitlements/audio_input=false +codesign/entitlements/camera=false +codesign/entitlements/location=false +codesign/entitlements/address_book=false +codesign/entitlements/calendars=false +codesign/entitlements/photos_library=false +codesign/entitlements/apple_events=false +codesign/entitlements/debugging=false +codesign/entitlements/app_sandbox/enabled=false +codesign/entitlements/app_sandbox/network_server=false +codesign/entitlements/app_sandbox/network_client=false +codesign/entitlements/app_sandbox/device_usb=false +codesign/entitlements/app_sandbox/device_bluetooth=false +codesign/entitlements/app_sandbox/files_downloads=0 +codesign/entitlements/app_sandbox/files_pictures=0 +codesign/entitlements/app_sandbox/files_music=0 +codesign/entitlements/app_sandbox/files_movies=0 +codesign/entitlements/app_sandbox/files_user_selected=0 +codesign/entitlements/app_sandbox/helper_executables=[] +codesign/custom_options=PackedStringArray() +notarization/notarization=0 +privacy/microphone_usage_description="" +privacy/microphone_usage_description_localized={} +privacy/camera_usage_description="" +privacy/camera_usage_description_localized={} +privacy/location_usage_description="" +privacy/location_usage_description_localized={} +privacy/address_book_usage_description="" +privacy/address_book_usage_description_localized={} +privacy/calendar_usage_description="" +privacy/calendar_usage_description_localized={} +privacy/photos_library_usage_description="" +privacy/photos_library_usage_description_localized={} +privacy/desktop_folder_usage_description="" +privacy/desktop_folder_usage_description_localized={} +privacy/documents_folder_usage_description="" +privacy/documents_folder_usage_description_localized={} +privacy/downloads_folder_usage_description="" +privacy/downloads_folder_usage_description_localized={} +privacy/network_volumes_usage_description="" +privacy/network_volumes_usage_description_localized={} +privacy/removable_volumes_usage_description="" +privacy/removable_volumes_usage_description_localized={} +privacy/tracking_enabled=false +privacy/tracking_domains=PackedStringArray() +privacy/collected_data/name/collected=false +privacy/collected_data/name/linked_to_user=false +privacy/collected_data/name/used_for_tracking=false +privacy/collected_data/name/collection_purposes=0 +privacy/collected_data/email_address/collected=false +privacy/collected_data/email_address/linked_to_user=false +privacy/collected_data/email_address/used_for_tracking=false +privacy/collected_data/email_address/collection_purposes=0 +privacy/collected_data/phone_number/collected=false +privacy/collected_data/phone_number/linked_to_user=false +privacy/collected_data/phone_number/used_for_tracking=false +privacy/collected_data/phone_number/collection_purposes=0 +privacy/collected_data/physical_address/collected=false +privacy/collected_data/physical_address/linked_to_user=false +privacy/collected_data/physical_address/used_for_tracking=false +privacy/collected_data/physical_address/collection_purposes=0 +privacy/collected_data/other_contact_info/collected=false +privacy/collected_data/other_contact_info/linked_to_user=false +privacy/collected_data/other_contact_info/used_for_tracking=false +privacy/collected_data/other_contact_info/collection_purposes=0 +privacy/collected_data/health/collected=false +privacy/collected_data/health/linked_to_user=false +privacy/collected_data/health/used_for_tracking=false +privacy/collected_data/health/collection_purposes=0 +privacy/collected_data/fitness/collected=false +privacy/collected_data/fitness/linked_to_user=false +privacy/collected_data/fitness/used_for_tracking=false +privacy/collected_data/fitness/collection_purposes=0 +privacy/collected_data/payment_info/collected=false +privacy/collected_data/payment_info/linked_to_user=false +privacy/collected_data/payment_info/used_for_tracking=false +privacy/collected_data/payment_info/collection_purposes=0 +privacy/collected_data/credit_info/collected=false +privacy/collected_data/credit_info/linked_to_user=false +privacy/collected_data/credit_info/used_for_tracking=false +privacy/collected_data/credit_info/collection_purposes=0 +privacy/collected_data/other_financial_info/collected=false +privacy/collected_data/other_financial_info/linked_to_user=false +privacy/collected_data/other_financial_info/used_for_tracking=false +privacy/collected_data/other_financial_info/collection_purposes=0 +privacy/collected_data/precise_location/collected=false +privacy/collected_data/precise_location/linked_to_user=false +privacy/collected_data/precise_location/used_for_tracking=false +privacy/collected_data/precise_location/collection_purposes=0 +privacy/collected_data/coarse_location/collected=false +privacy/collected_data/coarse_location/linked_to_user=false +privacy/collected_data/coarse_location/used_for_tracking=false +privacy/collected_data/coarse_location/collection_purposes=0 +privacy/collected_data/sensitive_info/collected=false +privacy/collected_data/sensitive_info/linked_to_user=false +privacy/collected_data/sensitive_info/used_for_tracking=false +privacy/collected_data/sensitive_info/collection_purposes=0 +privacy/collected_data/contacts/collected=false +privacy/collected_data/contacts/linked_to_user=false +privacy/collected_data/contacts/used_for_tracking=false +privacy/collected_data/contacts/collection_purposes=0 +privacy/collected_data/emails_or_text_messages/collected=false +privacy/collected_data/emails_or_text_messages/linked_to_user=false +privacy/collected_data/emails_or_text_messages/used_for_tracking=false +privacy/collected_data/emails_or_text_messages/collection_purposes=0 +privacy/collected_data/photos_or_videos/collected=false +privacy/collected_data/photos_or_videos/linked_to_user=false +privacy/collected_data/photos_or_videos/used_for_tracking=false +privacy/collected_data/photos_or_videos/collection_purposes=0 +privacy/collected_data/audio_data/collected=false +privacy/collected_data/audio_data/linked_to_user=false +privacy/collected_data/audio_data/used_for_tracking=false +privacy/collected_data/audio_data/collection_purposes=0 +privacy/collected_data/gameplay_content/collected=false +privacy/collected_data/gameplay_content/linked_to_user=false +privacy/collected_data/gameplay_content/used_for_tracking=false +privacy/collected_data/gameplay_content/collection_purposes=0 +privacy/collected_data/customer_support/collected=false +privacy/collected_data/customer_support/linked_to_user=false +privacy/collected_data/customer_support/used_for_tracking=false +privacy/collected_data/customer_support/collection_purposes=0 +privacy/collected_data/other_user_content/collected=false +privacy/collected_data/other_user_content/linked_to_user=false +privacy/collected_data/other_user_content/used_for_tracking=false +privacy/collected_data/other_user_content/collection_purposes=0 +privacy/collected_data/browsing_history/collected=false +privacy/collected_data/browsing_history/linked_to_user=false +privacy/collected_data/browsing_history/used_for_tracking=false +privacy/collected_data/browsing_history/collection_purposes=0 +privacy/collected_data/search_hhistory/collected=false +privacy/collected_data/search_hhistory/linked_to_user=false +privacy/collected_data/search_hhistory/used_for_tracking=false +privacy/collected_data/search_hhistory/collection_purposes=0 +privacy/collected_data/user_id/collected=false +privacy/collected_data/user_id/linked_to_user=false +privacy/collected_data/user_id/used_for_tracking=false +privacy/collected_data/user_id/collection_purposes=0 +privacy/collected_data/device_id/collected=false +privacy/collected_data/device_id/linked_to_user=false +privacy/collected_data/device_id/used_for_tracking=false +privacy/collected_data/device_id/collection_purposes=0 +privacy/collected_data/purchase_history/collected=false +privacy/collected_data/purchase_history/linked_to_user=false +privacy/collected_data/purchase_history/used_for_tracking=false +privacy/collected_data/purchase_history/collection_purposes=0 +privacy/collected_data/product_interaction/collected=false +privacy/collected_data/product_interaction/linked_to_user=false +privacy/collected_data/product_interaction/used_for_tracking=false +privacy/collected_data/product_interaction/collection_purposes=0 +privacy/collected_data/advertising_data/collected=false +privacy/collected_data/advertising_data/linked_to_user=false +privacy/collected_data/advertising_data/used_for_tracking=false +privacy/collected_data/advertising_data/collection_purposes=0 +privacy/collected_data/other_usage_data/collected=false +privacy/collected_data/other_usage_data/linked_to_user=false +privacy/collected_data/other_usage_data/used_for_tracking=false +privacy/collected_data/other_usage_data/collection_purposes=0 +privacy/collected_data/crash_data/collected=false +privacy/collected_data/crash_data/linked_to_user=false +privacy/collected_data/crash_data/used_for_tracking=false +privacy/collected_data/crash_data/collection_purposes=0 +privacy/collected_data/performance_data/collected=false +privacy/collected_data/performance_data/linked_to_user=false +privacy/collected_data/performance_data/used_for_tracking=false +privacy/collected_data/performance_data/collection_purposes=0 +privacy/collected_data/other_diagnostic_data/collected=false +privacy/collected_data/other_diagnostic_data/linked_to_user=false +privacy/collected_data/other_diagnostic_data/used_for_tracking=false +privacy/collected_data/other_diagnostic_data/collection_purposes=0 +privacy/collected_data/environment_scanning/collected=false +privacy/collected_data/environment_scanning/linked_to_user=false +privacy/collected_data/environment_scanning/used_for_tracking=false +privacy/collected_data/environment_scanning/collection_purposes=0 +privacy/collected_data/hands/collected=false +privacy/collected_data/hands/linked_to_user=false +privacy/collected_data/hands/used_for_tracking=false +privacy/collected_data/hands/collection_purposes=0 +privacy/collected_data/head/collected=false +privacy/collected_data/head/linked_to_user=false +privacy/collected_data/head/used_for_tracking=false +privacy/collected_data/head/collection_purposes=0 +privacy/collected_data/other_data_types/collected=false +privacy/collected_data/other_data_types/linked_to_user=false +privacy/collected_data/other_data_types/used_for_tracking=false +privacy/collected_data/other_data_types/collection_purposes=0 +ssh_remote_deploy/enabled=false +ssh_remote_deploy/host="user@host_ip" +ssh_remote_deploy/port="22" +ssh_remote_deploy/extra_args_ssh="" +ssh_remote_deploy/extra_args_scp="" +ssh_remote_deploy/run_script="#!/usr/bin/env bash +unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\" +open \"{temp_dir}/{exe_name}.app\" --args {cmd_args}" +ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash +kill $(pgrep -x -f \"{temp_dir}/{exe_name}.app/Contents/MacOS/{exe_name} {cmd_args}\") +rm -rf \"{temp_dir}\"" + +[preset.1] + +name="necra_prototype_linux" +platform="Linux" +runnable=true +advanced_options=false +dedicated_server=false +custom_features="" +export_filter="all_resources" +include_filter="" +exclude_filter="" +export_path="out/necra_prototype_linux.zip" +encryption_include_filters="" +encryption_exclude_filters="" +encrypt_pck=false +encrypt_directory=false +script_export_mode=2 + +[preset.1.options] + +custom_template/debug="" +custom_template/release="" +debug/export_console_wrapper=1 +binary_format/embed_pck=false +texture_format/s3tc_bptc=true +texture_format/etc2_astc=false +binary_format/architecture="x86_64" +ssh_remote_deploy/enabled=false +ssh_remote_deploy/host="user@host_ip" +ssh_remote_deploy/port="22" +ssh_remote_deploy/extra_args_ssh="" +ssh_remote_deploy/extra_args_scp="" +ssh_remote_deploy/run_script="#!/usr/bin/env bash +export DISPLAY=:0 +unzip -o -q \"{temp_dir}/{archive_name}\" -d \"{temp_dir}\" +\"{temp_dir}/{exe_name}\" {cmd_args}" +ssh_remote_deploy/cleanup_script="#!/usr/bin/env bash +kill $(pgrep -x -f \"{temp_dir}/{exe_name} {cmd_args}\") +rm -rf \"{temp_dir}\"" diff --git a/feedback_canvas_modulate.gd b/feedback_canvas_modulate.gd index 86e0f06..bd4bfa0 100644 --- a/feedback_canvas_modulate.gd +++ b/feedback_canvas_modulate.gd @@ -12,3 +12,8 @@ func show_hurt_feedback(): tween.tween_property(self, "color", Color(1.0,0,0, .2), .1) tween.tween_property(self, "color", default_color, .3) tween.tween_callback(func(): is_tweening = false) + +func fade_to_black() -> Tween: + var tween: Tween = create_tween() + tween.tween_property(self, "color", Color.BLACK, 3.0) + return tween diff --git a/grab_step.gd b/grab_step.gd new file mode 100644 index 0000000..168b06e --- /dev/null +++ b/grab_step.gd @@ -0,0 +1,41 @@ +class_name GrabStep +extends Node2D + +signal step_succeeded +signal step_failed + + +@export var is_skipped = false +@export_node_path("BatExtractionBat") var grabbing_target_path: NodePath + + +@onready var feedback_canvas_modulate: FeedbackCanvasModulate = %FeedbackCanvasModulate + + +var grabbing_target: BatExtractionBat +var pliers: Pliers + +func _ready(): + grabbing_target = get_node(grabbing_target_path) as BatExtractionBat + pliers = get_tree().get_first_node_in_group("grabber") as Pliers + pliers.pliers_used.connect(_on_pliers_used) + +func _on_pliers_used(top_area: Area2D): + if not grabbing_target.is_grabbed and top_area == grabbing_target.grab_area: + _target_grabbed() + elif grabbing_target.is_grabbed and top_area == (%BatDropoff as BatDropoff).dropoff_area: + _target_dropped() + else: + _hurt() + + +func _target_grabbed(): + grabbing_target.grab_bat() + +func _target_dropped(): + grabbing_target.drop_bat() + step_succeeded.emit() + +func _hurt(): + feedback_canvas_modulate.show_hurt_feedback() + %PatienceBar.value = clampi(%PatienceBar.value - 10, 0, 100) diff --git a/operation.gd b/operation.gd index 1da4cba..1e499a0 100644 --- a/operation.gd +++ b/operation.gd @@ -8,13 +8,26 @@ func _ready(): var children = get_children() for child in children: if child is Node2D: + if "is_skipped" in child and child.is_skipped: + _disable_node2d(child) + continue + steps.push_back(child) if child is CutSequence: child.sequence_succeeded.connect(_on_cut_sequence_succeeded.bind(child)) child.sequence_failed.connect(_on_cut_sequence_failed.bind(child)) _disable_node2d(child) - + elif child is GrabStep: + child.step_succeeded.connect(_on_grab_step_succeeded.bind(child)) + _disable_node2d(child) + active_step = steps[0] + if active_step is GrabStep: + _disable_node2d(%TestBody1) + _enable_node2d(%BatExtraction) + else: + _disable_node2d(%BatExtraction) + _enable_node2d(%TestBody1) _enable_node2d(active_step) @@ -22,8 +35,8 @@ func _on_cut_sequence_succeeded(cut_sequence: CutSequence): _disable_node2d(active_step) var next_index = steps.find(cut_sequence) + 1 if next_index == 4: - _enable_node2d(%TestBody2) - _disable_node2d(%BatExtraction) + _enable_node2d(%BatExtraction) + _disable_node2d(%TestBody1) active_step = steps[next_index] _enable_node2d(active_step) @@ -32,6 +45,11 @@ func _on_cut_sequence_succeeded(cut_sequence: CutSequence): func _on_cut_sequence_failed(cut_sequence: CutSequence): pass +func _on_grab_step_succeeded(grab_step: GrabStep): + await (%FeedbackCanvasModulate as FeedbackCanvasModulate).fade_to_black().finished + get_tree().reload_current_scene() + + func _enable_node2d(node: Node2D): node.set_deferred("visible", true) node.set_deferred("process_mode", Node.PROCESS_MODE_INHERIT) diff --git a/pickup_area.gd b/pickup_area.gd new file mode 100644 index 0000000..4406de0 --- /dev/null +++ b/pickup_area.gd @@ -0,0 +1,50 @@ +class_name PickupArea +extends Area2D + +signal picked_up() +signal dropped() + + +@onready var dropoff_area: Area2D = %UtilsDropoff.get_node("Area2D") + + +var is_clickable = false +var is_picked_up = false +var is_dropable = false +var parent: Node2D + +func _ready(): + mouse_entered.connect(func(): is_clickable = true) + mouse_exited.connect(func(): is_clickable = false) + parent = get_parent() as Node2D + +func _input(event): + if event is InputEventMouseButton and event.is_pressed(): + if is_picked_up and is_dropable: + _drop() + elif not is_picked_up and is_clickable: + _pickup() + if is_picked_up and event is InputEventMouseMotion: + parent.global_position = event.global_position + +func _exit_tree(): + Input.mouse_mode = Input.MOUSE_MODE_VISIBLE + +func _on_dropoff_area_entered(area): + if area == dropoff_area: + is_dropable = true + +func _on_dropoff_area_exited(area): + if area == dropoff_area: + is_dropable = false + + +func _pickup(): + is_picked_up = true + Input.mouse_mode = Input.MOUSE_MODE_HIDDEN + picked_up.emit() + +func _drop(): + is_picked_up = false + Input.mouse_mode = Input.MOUSE_MODE_VISIBLE + dropped.emit() diff --git a/pickup_area.tscn b/pickup_area.tscn new file mode 100644 index 0000000..6ffe2b6 --- /dev/null +++ b/pickup_area.tscn @@ -0,0 +1,11 @@ +[gd_scene load_steps=2 format=3 uid="uid://c3of67m4ic212"] + +[ext_resource type="Script" path="res://pickup_area.gd" id="1_6qsiy"] + +[node name="PickupArea" type="Area2D"] +collision_layer = 2 +collision_mask = 2 +script = ExtResource("1_6qsiy") + +[connection signal="area_entered" from="." to="." method="_on_dropoff_area_entered"] +[connection signal="area_exited" from="." to="." method="_on_dropoff_area_exited"] diff --git a/pliers.gd b/pliers.gd new file mode 100644 index 0000000..762b08e --- /dev/null +++ b/pliers.gd @@ -0,0 +1,18 @@ +class_name Pliers +extends Sprite2D + +signal pliers_used(top_area: Area2D) + +@onready var pickup_area: PickupArea = $PickupArea +@onready var grab_area: Area2D = $GrabArea + + + +func _input(event): + if event is InputEventMouseButton and pickup_area.is_picked_up and event.is_pressed(): + var areas = grab_area.get_overlapping_areas() + print("Before sorting:", areas) + if not areas.is_empty(): + areas.sort_custom(func(a: Area2D, b: Area2D): return a.priority > b.priority) + print("After sorting:", areas) + pliers_used.emit(areas[0]) diff --git a/pliers.png b/pliers.png new file mode 100644 index 0000000000000000000000000000000000000000..23b8446f9e068ebf7a16ef199a11bea6cbaf2778 GIT binary patch literal 2299 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