commit 5906f248f428e8ed669723c79bdde3b0c613b2ca Author: ronnie Date: Wed Apr 15 22:22:56 2026 -0400 init diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..5de9f75 --- /dev/null +++ b/.clang-format @@ -0,0 +1,17 @@ +BasedOnStyle: LLVM +AlignAfterOpenBracket: DontAlign +AlignOperands: DontAlign +AlignTrailingComments: +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortFunctionsOnASingleLine: Inline +BreakConstructorInitializers: AfterColon +ColumnLimit: 0 +ContinuationIndentWidth: 2 +IndentCaseLabels: true +IndentWidth: 2 +InsertBraces: true +KeepEmptyLinesAtTheStartOfBlocks: false +RemoveSemicolon: true +SpacesInLineCommentPrefix: +TabWidth: 2 +UseTab: Always diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..9fdf044 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,415 @@ +# --------------------------------------------------------------------------- # +# Chickensoft C# Style — .editorconfig # +# --------------------------------------------------------------------------- # +# Godot-friendly coding style with a bit of Dart-style flair thrown in. # +# --------------------------------------------------------------------------- # +# # +# # +# ╓╗_▄╗_╓▄_ # +# ▄▄╟▓▓▓▓▓▓▓▓ # +# ╙▓▓▓▀▀╠╠╦╦╓,_ # +# ,φ╠╠╠╠╠╠╠╠╠╠▒╥ # +# φ╠╠╠╠╠╠╠╠╠╠╠╠╠╠╦ # +# @╠╠╫▌╠╟▌╠╠╠╠╠╠╠╠╠ # +# ╠╠╠▄▄▄▒╠╠╠╠╠╠╠╠╠╠b # +# ╠╠╨███▌╠╠╠╠╠╠╠▒╠╠▒_ ç╓ # +# ╠╠╠╠▒▒╠╠╠╠╠╠╠╠▒Å▄╠╬▒φ╩ε # +# ╚╠╠╠╠╠╠╠╠╠╠╠▒█▒╫█Å╠╠╩ # +# ╠╠╠╠╠╠╠╠╠╠╠╠╠╟╫▒╠╠╩ # +# ╙╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╜ # +# ╙╚╠╠╠╠╠╠╠╠╩╙ # +# ╒ µ # +# ▌ ▓ # +# ^▀▀ "▀ª # +# # +# # +# --------------------------------------------------------------------------- # +# +# Based on: +# - https://github.com/RehanSaeed/EditorConfig/blob/main/.editorconfig +# - https://gist.github.com/FaronBracy/155d8d7ad98b4ceeb526b9f47543db1b +# - various other gists floating around :) +# +# Have a problem? Encounter an issue? +# Come visit our Discord and let us know! https://discord.gg/MjA6HUzzAE +# +# Based on https://github.com/RehanSaeed/EditorConfig/blob/main/.editorconfig +# and https://gist.github.com/FaronBracy/155d8d7ad98b4ceeb526b9f47543db1b + +# This file is the top-most EditorConfig file +root = true + +# All Files +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true +end_of_line = lf + +########################################## +# File Extension Settings +########################################## + +# GDScript Files +[*.{gd,gdshader,gdshaderinc}] +indent_style = tab + +# Visual Studio Solution Files +[*.sln] +indent_style = tab + +# Visual Studio XML Project Files +[*.{csproj,vbproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# XML Configuration Files +[*.{xml,config,props,targets,nuspec,resx,ruleset,vsixmanifest,vsct}] +indent_size = 2 + +# JSON Files +[*.{json,json5,webmanifest}] +indent_size = 2 + +# YAML Files +[*.{yml,yaml}] +indent_size = 2 + +# Markdown Files +[*.{md,mdx}] +trim_trailing_whitespace = false + +# Web Files +[*.{htm,html,js,jsm,ts,tsx,cjs,cts,ctsx,mjs,mts,mtsx,css,sass,scss,less,pcss,svg,vue}] +indent_size = 2 + +# Batch Files +[*.{cmd,bat}] +end_of_line = crlf + +# Makefiles +[Makefile] +indent_style = tab + +[*{_Generated.cs,.g.cs,.generated.cs}] +# Ignore a lack of documentation for generated code. Doesn't apply to builds, +# just to viewing generation output. +dotnet_diagnostic.CS1591.severity = none + +########################################## +# Default .NET Code Style Severities +########################################## + +[*.cs] +# Default Severity for all .NET Code Style rules below +dotnet_analyzer_diagnostic.severity = warning + +########################################## +# Language Rules +########################################## + +# .NET Style Rules + +# "this." and "Me." qualifiers +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_property = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_event = false + +# Language keywords instead of framework type names for type references +dotnet_style_predefined_type_for_locals_parameters_members = true:warning +dotnet_style_predefined_type_for_member_access = true:warning + +# Modifier preferences +dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:warning +visual_basic_preferred_modifier_order = Partial,Default,Private,Protected,Public,Friend,NotOverridable,Overridable,MustOverride,Overloads,Overrides,MustInherit,NotInheritable,Static,Shared,Shadows,ReadOnly,WriteOnly,Dim,Const,WithEvents,Widening,Narrowing,Custom,Async:warning +dotnet_style_readonly_field = true:warning + +# Parentheses preferences +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:warning +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:warning + +# Expression-level preferences +dotnet_style_object_initializer = true:warning +dotnet_style_collection_initializer = true:warning +dotnet_style_explicit_tuple_names = true:warning +dotnet_style_prefer_inferred_tuple_names = true:warning +dotnet_style_prefer_inferred_anonymous_type_member_names = true:warning +dotnet_style_prefer_auto_properties = true:warning +dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion +dotnet_diagnostic.IDE0045.severity = suggestion +dotnet_style_prefer_conditional_expression_over_return = true:suggestion +dotnet_diagnostic.IDE0046.severity = suggestion +dotnet_style_prefer_compound_assignment = true:warning +dotnet_style_prefer_simplified_interpolation = true:warning +dotnet_style_prefer_simplified_boolean_expressions = true:warning + +# Null-checking preferences +dotnet_style_coalesce_expression = true:warning +dotnet_style_null_propagation = true:warning +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:warning + +# Unused parameters and methods +dotnet_diagnostic.IDE0060.severity = warn +dotnet_diagnostic.IDE0051.severity = warn + +# File header preferences +# Keep operators at end of line when wrapping. +dotnet_style_operator_placement_when_wrapping = end_of_line +csharp_style_prefer_null_check_over_type_check = true:warning + +# Code block preferences +csharp_prefer_braces = true:warning +csharp_prefer_simple_using_statement = true:suggestion +dotnet_diagnostic.IDE0063.severity = suggestion + +# C# Style Rules + +# 'var' preferences +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = true:warning +# Expression-bodied members +csharp_style_expression_bodied_methods = when_on_single_line:warning +csharp_style_expression_bodied_constructors = false:warning +csharp_style_expression_bodied_operators = true:warning +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_accessors = true:warning +csharp_style_expression_bodied_lambdas = true:warning +csharp_style_expression_bodied_local_functions = true:warning +# Pattern matching preferences +csharp_style_pattern_matching_over_is_with_cast_check = true:warning +csharp_style_pattern_matching_over_as_with_null_check = true:warning +csharp_style_prefer_switch_expression = true:warning +csharp_style_prefer_pattern_matching = true:warning +csharp_style_prefer_not_pattern = true:warning +# Expression-level preferences +csharp_style_inlined_variable_declaration = true:warning +csharp_prefer_simple_default_expression = true:warning +csharp_style_pattern_local_over_anonymous_function = true:warning +csharp_style_deconstructed_variable_declaration = true:warning +csharp_style_prefer_index_operator = true:warning +csharp_style_prefer_range_operator = true:warning +csharp_style_implicit_object_creation_when_type_is_apparent = true:warning +# "Null" checking preferences +csharp_style_throw_expression = true:warning +csharp_style_conditional_delegate_call = true:warning +# 'using' directive preferences +csharp_using_directive_placement = inside_namespace:warning +# Use discard variable for unused expression values. +csharp_style_unused_value_expression_statement_preference = discard_variable +csharp_style_unused_value_assignment_preference = discard_variable + +########################################## +# Formatting Rules +########################################## + +# .NET formatting rules + +# Organize using directives +dotnet_sort_system_directives_first = true +dotnet_separate_import_directive_groups = false + +# Dotnet namespace options +# +# We don't care about namespaces matching folder structure. Games and apps +# are complicated and you are free to organize them however you like. Change +# this if you want to enforce it. +dotnet_style_namespace_match_folder = false +dotnet_diagnostic.IDE0130.severity = none + +# C# formatting rules + +# Newline options +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_between_query_expression_clauses = true + +# Indentation options +csharp_indent_switch_labels = true +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = no_change +csharp_indent_block_contents = true +csharp_indent_braces = false + +# Spacing options +csharp_space_after_cast = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_parentheses = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_around_binary_operators = before_and_after +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_after_comma = true +csharp_space_before_comma = false +csharp_space_after_dot = false +csharp_space_before_dot = false +csharp_space_after_semicolon_in_for_statement = true +csharp_space_before_semicolon_in_for_statement = false +csharp_space_around_declaration_statements = false +csharp_space_before_open_square_brackets = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_square_brackets = false + +# Wrap options +csharp_preserve_single_line_statements = false +csharp_preserve_single_line_blocks = true + +# Namespace options +csharp_style_namespace_declarations = file_scoped:warning + +########################################## +# Unnecessary Code Rules +########################################## + +# .NET Unnecessary code rules + +dotnet_code_quality_unused_parameters = non_public:suggestion +dotnet_remove_unnecessary_suppression_exclusions = none + +########################################## +# .NET Naming Rules +########################################## + +########################################## +# Chickensoft Naming Conventions & Styles +# These deviate heavily from Microsoft's Official Naming Conventions. +########################################## + +# Allow underscores in names. +dotnet_diagnostic.CA1707.severity = none + +# Styles +dotnet_naming_style.pascal_case_style.capitalization = pascal_case + +dotnet_naming_style.upper_case_style.capitalization = all_upper +dotnet_naming_style.upper_case_style.word_separator = _ + +dotnet_naming_style.camel_case_style.capitalization = camel_case + +dotnet_naming_style.camel_case_underscore_style.required_prefix = _ +dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case + +# Use uppercase for all constant fields. +dotnet_naming_rule.constants_uppercase.severity = suggestion +dotnet_naming_rule.constants_uppercase.symbols = constant_fields +dotnet_naming_rule.constants_uppercase.style = upper_case_style +dotnet_naming_symbols.constant_fields.applicable_kinds = field +dotnet_naming_symbols.constant_fields.applicable_accessibilities = * +dotnet_naming_symbols.constant_fields.required_modifiers = const + +# Non-public fields should be _camelCase +dotnet_naming_rule.non_public_fields_under_camel.severity = suggestion +dotnet_naming_rule.non_public_fields_under_camel.symbols = non_public_fields +dotnet_naming_rule.non_public_fields_under_camel.style = camel_case_underscore_style +dotnet_naming_symbols.non_public_fields.applicable_kinds = field +dotnet_naming_symbols.non_public_fields.required_modifiers = +dotnet_naming_symbols.non_public_fields.applicable_accessibilities = private,private_protected,internal,protected,protected_internal + +# Public fields should be PascalCase +dotnet_naming_rule.public_fields_pascal.severity = suggestion +dotnet_naming_rule.public_fields_pascal.symbols = public_fields +dotnet_naming_rule.public_fields_pascal.style = pascal_case_style +dotnet_naming_symbols.public_fields.applicable_kinds = field +dotnet_naming_symbols.public_fields.required_modifiers = +dotnet_naming_symbols.public_fields.applicable_accessibilities = public + +# Async methods should have "Async" suffix. +# Disabled because it makes tests too verbose. +# dotnet_naming_style.end_in_async.required_suffix = Async +# dotnet_naming_style.end_in_async.capitalization = pascal_case +# dotnet_naming_rule.methods_end_in_async.symbols = methods_async +# dotnet_naming_rule.methods_end_in_async.style = end_in_async +# dotnet_naming_rule.methods_end_in_async.severity = warning +# dotnet_naming_symbols.methods_async.applicable_kinds = method +# dotnet_naming_symbols.methods_async.required_modifiers = async +# dotnet_naming_symbols.methods_async.applicable_accessibilities = * + +########################################## +# Other Naming Rules +########################################## + +# All of the following must be PascalCase: +dotnet_naming_symbols.element_group.applicable_kinds = namespace, class, enum, struct, delegate, event, method, property +dotnet_naming_rule.element_rule.symbols = element_group +dotnet_naming_rule.element_rule.style = pascal_case_style +dotnet_naming_rule.element_rule.severity = warning + +# Interfaces use PascalCase and are prefixed with uppercase 'I' +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +dotnet_naming_style.prefix_interface_with_i_style.capitalization = pascal_case +dotnet_naming_style.prefix_interface_with_i_style.required_prefix = I +dotnet_naming_symbols.interface_group.applicable_kinds = interface +dotnet_naming_rule.interface_rule.symbols = interface_group +dotnet_naming_rule.interface_rule.style = prefix_interface_with_i_style +dotnet_naming_rule.interface_rule.severity = warning + +# Generics Type Parameters use PascalCase and are prefixed with uppercase 'T' +# https://docs.microsoft.com/dotnet/standard/design-guidelines/names-of-classes-structs-and-interfaces +dotnet_naming_style.prefix_type_parameters_with_t_style.capitalization = pascal_case +dotnet_naming_style.prefix_type_parameters_with_t_style.required_prefix = T +dotnet_naming_symbols.type_parameter_group.applicable_kinds = type_parameter +dotnet_naming_rule.type_parameter_rule.symbols = type_parameter_group +dotnet_naming_rule.type_parameter_rule.style = prefix_type_parameters_with_t_style +dotnet_naming_rule.type_parameter_rule.severity = warning + +# Function parameters use camelCase +# https://docs.microsoft.com/dotnet/standard/design-guidelines/naming-parameters +dotnet_naming_symbols.parameters_group.applicable_kinds = parameter +dotnet_naming_rule.parameters_rule.symbols = parameters_group +dotnet_naming_rule.parameters_rule.style = camel_case_style +dotnet_naming_rule.parameters_rule.severity = warning + +# Anything not specified uses camel case. +dotnet_naming_rule.unspecified_naming.severity = warning +dotnet_naming_rule.unspecified_naming.symbols = unspecified +dotnet_naming_rule.unspecified_naming.style = camel_case_style +dotnet_naming_symbols.unspecified.applicable_kinds = * +dotnet_naming_symbols.unspecified.applicable_accessibilities = * + +########################################## +# Chickensoft Rule Overrides +########################################## + +# Allow protected fields. +dotnet_diagnostic.CA1051.severity = none +# Don't warn about checking values that are supposedly never null. Sometimes +# they are actually null. +dotnet_diagnostic.CS8073.severity = none +# Allow expression values to go unused, even without discard variable. +# Otherwise, using Moq would be way too verbose. +dotnet_diagnostic.IDE0058.severity = none +# Allow me to use the word Collection if I want. +dotnet_diagnostic.CA1711.severity = none +# Don't warn about using reserved keywords (e.g., methods named "On") +dotnet_diagnostic.CA1716.severity = none +# Don't warn about public methods that can be marked static +# (tests frequently don't access member data, and GoDotTest won't call static methods) +dotnet_code_quality.CA1822.api_surface = private +# No primary constructors — not supported well by tooling. +dotnet_diagnostic.IDE0290.severity = none +# Let me write dumb if statements for readability. +dotnet_diagnostic.IDE0046.severity = none +# DO make me populate a *switch expression* +dotnet_diagnostic.IDE0072.severity = warning +# Don't make me populate a *switch statement* +dotnet_diagnostic.IDE0010.severity = none +# Make local functions static +dotnet_diagnostic.IDE0062.severity = warning +# Don't make me use properties if I don't want to. +dotnet_diagnostic.IDE0032.severity = none diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..08e6520 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,40 @@ +# Normalize EOL for all files that Git considers text files. +* text=auto eol=lf + +# Image formats +*.bmp filter=lfs diff=lfs merge=lfs -text +*.dds filter=lfs diff=lfs merge=lfs -text +*.exr filter=lfs diff=lfs merge=lfs -text +*.hdr filter=lfs diff=lfs merge=lfs -text +*.jpg filter=lfs diff=lfs merge=lfs -text +*.jpeg filter=lfs diff=lfs merge=lfs -text +*.png filter=lfs diff=lfs merge=lfs -text +*.tga filter=lfs diff=lfs merge=lfs -text +*.webp filter=lfs diff=lfs merge=lfs -text + +# Audio and Video formats +*.mp3 filter=lfs diff=lfs merge=lfs -text +*.wav filter=lfs diff=lfs merge=lfs -text +*.ogg filter=lfs diff=lfs merge=lfs -text +*.ogx filter=lfs diff=lfs merge=lfs -text +*.ogv filter=lfs diff=lfs merge=lfs -text + +# 3D formats +*.gltf filter=lfs diff=lfs merge=lfs -text +*.blend filter=lfs diff=lfs merge=lfs -text +*.blend1 filter=lfs diff=lfs merge=lfs -text +*.glb filter=lfs diff=lfs merge=lfs -text +*.dae filter=lfs diff=lfs merge=lfs -text +*.fbx filter=lfs diff=lfs merge=lfs -text + +# Build +*.dll filter=lfs diff=lfs merge=lfs -text + +# Packaging +*.zip filter=lfs diff=lfs merge=lfs -text +*.7z filter=lfs diff=lfs merge=lfs -text +*.gz filter=lfs diff=lfs merge=lfs -text +*.rar filter=lfs diff=lfs merge=lfs -text +*.tar filter=lfs diff=lfs merge=lfs -text +*.file filter=lfs diff=lfs merge=lfs -text +*.dylib filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/spellcheck.yaml b/.github/workflows/spellcheck.yaml new file mode 100644 index 0000000..9f24360 --- /dev/null +++ b/.github/workflows/spellcheck.yaml @@ -0,0 +1,30 @@ +name: "🧑‍🏫 Spellcheck" +on: + push: + pull_request: + +jobs: + # We previously used the cspell action, but it didn't seem to properly respect + # the cspell.json file. + spellcheck: + name: "🧑‍🏫 Spellcheck" + # Prevents duplicate workflows from running on PR's that originate from the + # repository itself. + if: > + github.event_name != 'pull_request' || + github.event.pull_request.head.repo.full_name != + github.event.pull_request.base.repo.full_name + runs-on: ubuntu-latest + defaults: + run: + working-directory: "." + steps: + - uses: actions/checkout@v6 + name: 🧾 Checkout + + - uses: streetsidesoftware/cspell-action@v8 + name: 📝 Check Spelling + with: + config: "./cspell.json" + incremental_files_only: false + root: "." diff --git a/.github/workflows/version_change.yaml b/.github/workflows/version_change.yaml new file mode 100644 index 0000000..e23976f --- /dev/null +++ b/.github/workflows/version_change.yaml @@ -0,0 +1,35 @@ +name: "🗂 Version Change" +on: + workflow_dispatch: + inputs: + version: + description: "Version (no 'v' prefix)" + required: true + +jobs: + create_version_pull_request: + name: "🗂 Create Version Pull Request" + runs-on: ubuntu-latest + steps: + - name: "🧾 Checkout" + uses: actions/checkout@v6 + + - name: "📝 Change Version" + uses: vers-one/dotnet-project-version-updater@v1.7 + with: + file: "ChickenGameTest.csproj" + version: ${{ github.event.inputs.version }} + + - name: "⤴️ Create Pull Request" + uses: peter-evans/create-pull-request@v8 + with: + token: ${{ secrets.GITHUB_TOKEN }} + branch: version/${{ github.event.inputs.version }} + commit-message: > + update version to ${{ github.event.inputs.version }} + title: > + "chore(project): update version to + ${{ github.event.inputs.version }}" + body: > + chore(project): update the version to + ${{ github.event.inputs.version }}. diff --git a/.github/workflows/visual_tests.yaml b/.github/workflows/visual_tests.yaml new file mode 100644 index 0000000..2c2b2c8 --- /dev/null +++ b/.github/workflows/visual_tests.yaml @@ -0,0 +1,87 @@ +name: 🖼 Visual Tests +on: + push: + pull_request: + +jobs: + visual_tests: + name: 🖼 Visual Tests with ${{ matrix.render-driver }} + runs-on: ubuntu-latest + # Only run the workflow if it's not a PR or if it's a PR from a fork. + # This prevents duplicate workflows from running on PR's that originate + # from the repository itself. + if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name + env: + DOTNET_CLI_TELEMETRY_OPTOUT: true + DOTNET_NOLOGO: true + strategy: + # Don't cancel other runners if one fails. + fail-fast: false + matrix: + # Put the rendering drivers you want to use for tests here. + render-driver: [vulkan] # also: opengl3 + defaults: + run: + # Use bash shells on all platforms. + shell: bash + steps: + - name: 🧾 Checkout + uses: actions/checkout@v6 + with: + # If using git-lfs (large file storage), this ensures that your files + # are checked out properly. + lfs: true + # Make sure any git submodules are checked out as well. + submodules: 'recursive' + + - name: 💽 Setup .NET SDK + uses: actions/setup-dotnet@v5 + with: + # Use the .NET SDK from global.json in the root of the repository. + global-json-file: global.json + + - name: 📦 Restore Dependencies + run: dotnet restore + + - name: 💾 Add Graphics Driver Emulators Source + run: | + sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list + sudo add-apt-repository -n ppa:kisak/kisak-mesa + + - name: 💾 Install Graphics Driver Emulators + uses: awalsh128/cache-apt-pkgs-action@latest + with: + packages: mesa-vulkan-drivers binutils x11-xserver-utils + version: 1.0 + + - name: 🤖 Setup Godot + uses: chickensoft-games/setup-godot@v2 + with: + # Version must include major, minor, and patch, and be >= 4.0.0 + # Pre-release label is optional. + # + # In this case, we are using the version from global.json. + # + # This allows checks on renovatebot PR's to succeed whenever + # renovatebot updates the Godot SDK version. + version: global.json + + - name: 🧑‍🔬 Generate .NET Bindings + run: godot --headless --build-solutions --quit || exit 0 + + - name: 🌋 Run Tests in Godot + run: | + xvfb-run godot --audio-driver Dummy --rendering-driver ${{ matrix.render-driver }} --run-tests --quit-on-finish + + # The --coverage flag is used by GoDotTest to control the exit code + # of Godot by force-exiting through C#. + # + # Since Godot sometimes exits with non-zero exit codes (even on success), + # adding this flag to the above command may allow GoDotTest to ensure that + # this step will only fail when the tests fail, and not because Godot didn't + # exit cleanly. + # + # However, note that the --coverage flag can sometimes cause other failures + # by forcing Godot to exit before it can clean up its resources completely. + + echo "Finished running tests in Godot with emulated ${{ matrix.render-driver }} graphics." diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..185d526 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +nupkg/ + +coverage/* +!coverage/.gdignore + +.godot/ +bin/ +obj/ +.generated/ +.vs/ +.DS_Store + +*.old +*.translation +addons/* +!addons/.editorconfig +.addons/* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..a81e137 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "assets"] + path = assets + url = http://192.168.1.4:3000/Ronnie/FoodFactoryAssets diff --git a/.vscode/chickensoft.code-snippets b/.vscode/chickensoft.code-snippets new file mode 100644 index 0000000..56b55d5 --- /dev/null +++ b/.vscode/chickensoft.code-snippets @@ -0,0 +1,115 @@ +{ + // Place your workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and + // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope + // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is + // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. + // Placeholders with the same ids are connected. + // Example: + // "Print to console": { + // "scope": "javascript,typescript", + // "prefix": "log", + // "body": [ + // "console.log('$1');", + // "$2" + // ], + // "description": "Log output to console" + // } + "Chickensoft AutoNode": { + "scope": "csharp", + "isFileTemplate": true, + "prefix": [ "autonode" ], + "body": [ + "namespace ${1:$WORKSPACE_NAME};", + "", + "using Chickensoft.AutoInject;", + "using Chickensoft.Introspection;", + "using Godot;", + "", + "[Meta(typeof(IAutoNode))]", + "public partial class ${2:$TM_FILENAME_BASE} : ${3:Node} {", + " public override void _Notification(int what) => this.Notify(what);", + "$0", + "}", + "" + ], + "description": "Chickensoft AutoNode template" + }, + "Chickensoft AutoInject Provision": { + "scope": "csharp", + "prefix": [ "provide" ], + "body": [ + "${1:ProvisionType} IProvide<${1:ProvisionType}>.Value() => ${2:ProvisionValue};" + ], + "description": "Chickensoft AutoInject IProvide implementation" + }, + "Chickensoft AutoInject Dependency": { + "scope": "csharp", + "prefix": [ "depend" ], + "body": [ + "[Dependency] ${1:public} ${2:DependencyType} ${3:DependencyValue} => this.DependOn<${2:DependencyType}>();" + ], + "description": "Chickensoft AutoInject Dependency attribute" + }, + "Chickensoft AutoProp": { + "scope": "csharp", + "prefix": [ "autoprop" ], + "body": [ + "public IAutoProp<${1:PropType}> ${2:PublicPropName} => ${3:_privatePropName};", + "private readonly AutoProp<${1:PropType}> ${3:_privatePropName} = new($0);" + ], + "description": "Chickensoft Collections AutoProp attribute" + }, + "Chickensoft LogicBlock": { + "scope": "csharp", + "isFileTemplate": true, + "prefix": [ "logicblock", "lb" ], + "body": [ + "namespace ${1:$WORKSPACE_NAME};", + "", + "using Chickensoft.Introspection;", + "using Chickensoft.LogicBlocks;", + "", + "[Meta, LogicBlock(typeof(State), Diagram = true)]", + "public partial class ${2:$TM_FILENAME_BASE} : LogicBlock<${2:$TM_FILENAME_BASE}.State> {", + " public override Transition GetInitialState() => To<${3:State}>();", + "$0", + " public abstract partial record State : StateLogic;", + "}", + "" + ], + "description": "Chickensoft LogicBlock template" + }, + "Chickensoft LogicBlock Inputs": { + "scope": "csharp", + "isFileTemplate": true, + "prefix": [ "logicblock-input", "lbin" ], + "body": [ + "namespace ${1:$WORKSPACE_NAME};", + "", + "public partial class ${2:${TM_FILENAME_BASE/([a-zA-Z0-9_]*)([\\.]Input)/$1/}} {", + " public static class Input {", + " public readonly record struct ${3:MyInput};", + " }", + "}", + "" + ], + "description": "Chickensoft LogicBlock Input class" + }, + "Chickensoft LogicBlock Outputs": { + "scope": "csharp", + "isFileTemplate": true, + "prefix": [ "logicblock-output", "lbout" ], + "body": [ + "namespace ${1:$WORKSPACE_NAME};", + "", + "public partial class ${2:${TM_FILENAME_BASE/([a-zA-Z0-9_]*)([\\.]Output)/$1/}} {", + " public static class Output {", + " public readonly record struct ${3:MyOutput};", + " }", + "}", + "" + ], + "description": "Chickensoft LogicBlock Output class" + } +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..38baf27 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,29 @@ +{ + "recommendations": [ + "alfish.godot-files", + "bierner.emojisense", + "christian-kohler.path-intellisense", + "codezombiech.gitignore", + "DavidAnson.vscode-markdownlint", + "EditorConfig.EditorConfig", + "edwinhuish.better-comments-next", + "emeraldwalk.runonsave", + "fernandoescolar.vscode-solution-explorer", + "geequlim.godot-tools", + "gurumukhi.selected-lines-count", + "IBM.output-colorizer", + "jebbs.plantuml", + "josefpihrt-vscode.roslynator", + "ms-dotnettools.csharp", + "ms-dotnettools.vscode-dotnet-runtime", + "muhammad-sammy.csharp", + "oderwat.indent-rainbow", + "pflannery.vscode-versionlens", + "qcz.text-power-tools", + "selcukermaya.se-csproj-extensions", + "stkb.rewrap", + "streetsidesoftware.code-spell-checker", + "tintoy.msbuild-project-tools", + "yzhang.markdown-all-in-one", + ] +} diff --git a/.vscode/godot.code-snippets b/.vscode/godot.code-snippets new file mode 100644 index 0000000..c273fb1 --- /dev/null +++ b/.vscode/godot.code-snippets @@ -0,0 +1,34 @@ +{ + // Place your workspace snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and + // description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope + // is left empty or omitted, the snippet gets applied to all languages. The prefix is what is + // used to trigger the snippet and the body will be expanded and inserted. Possible variables are: + // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. + // Placeholders with the same ids are connected. + // Example: + // "Print to console": { + // "scope": "javascript,typescript", + // "prefix": "log", + // "body": [ + // "console.log('$1');", + // "$2" + // ], + // "description": "Log output to console" + // } + "Godot [Export]": { + "scope": "csharp", + "prefix": [ "export" ], + "body": [ + "[Export] ${1:public} ${2:Node} ${3:MyExport} { get; set; } = default!;" + ], + "description": "Godot [Export] attribute" + }, + "Godot [Signal]": { + "scope": "csharp", + "prefix": [ "signal" ], + "body": [ + "[Signal] public delegate void ${1:My}EventHandler($2);" + ], + "description": "Godot [Signal] attribute" + } +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6c2b95b --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,107 @@ +{ + "version": "0.2.0", + "configurations": [ + // For these launch configurations to work, you need to setup a GODOT + // environment variable. On mac or linux, this can be done by adding + // the following to your .zshrc, .bashrc, or .bash_profile file: + // export GODOT="/Applications/Godot.app/Contents/MacOS/Godot" + { + "name": "🕹 Debug Game", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build-without-tests", + "program": "${env:GODOT}", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "integratedTerminal" + }, + { + "name": "🕹 Debug Game (VSCodium)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build-without-tests", + "program": "", + "internalConsoleOptions": "openOnSessionStart", + "pipeTransport": { + "debuggerPath": "${extensionInstallFolder:muhammad-sammy.csharp}/.debugger/netcoredbg/netcoredbg", + "pipeCwd": "${workspaceFolder}", + "pipeProgram": "${env:GODOT}", + "pipeArgs": [ + "--debug" + ] + }, + "osx": { + "pipeTransport": { + // netcoredbg for Apple Silicon isn't included with the VSCodium C# + // extension. You must clone it, build it, and setup the path to it. + // You'll need homebrew, cmake, and clang installed. + // + // --------------------------------------------------------------- // + // + // git clone https://github.com/Samsung/netcoredbg.git + // cd netcoredbg + // mkdir build + // cd build + // CC=clang CXX=clang++ cmake .. -DCMAKE_INSTALL_PREFIX=$PWD/../bin + // + // In your ~/.zshrc file, add the following line and adjust the path: + // + // export NETCOREDBG="/path/to/netcoredbg/bin/netcoredbg" + // + "debuggerPath": "${env:NETCOREDBG}", + "pipeCwd": "${workspaceFolder}", + "pipeProgram": "${env:GODOT}", + "pipeArgs": [ + "--debug" + ] + } + }, + }, + // Debug the scene that matches the name of the currently open *.cs file + // (if there's a scene with the same name in the same directory). + { + "name": "🎭 Debug Current Scene", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build-without-tests", + "program": "${env:GODOT}", + "args": [ + "${fileDirname}/${fileBasenameNoExtension}.tscn" + ], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "integratedTerminal" + }, + { + "name": "🧪 Debug Tests", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${env:GODOT}", + "args": [ + // These command line flags are used by GoDotTest to run tests. + "--run-tests", + "--quit-on-finish" + ], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "integratedTerminal" + }, + { + "name": "🔬 Debug Current Test", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${env:GODOT}", + "args": [ + // These command line flags are used by GoDotTest to run tests. + "--run-tests=${fileBasenameNoExtension}", + "--quit-on-finish" + ], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "integratedTerminal" + }, + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9a81d2a --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,187 @@ +{ + "[csharp]": { + "editor.codeActionsOnSave": { + "source.addMissingImports": "explicit", + "source.fixAll": "explicit", + "source.organizeImports": "explicit" + }, + "editor.formatOnPaste": true, + "editor.formatOnSave": true, + "editor.formatOnType": false + }, + "emeraldwalk.runonsave": { + "commands": [ + { + // run `clang-format` on Godot shader files when saving + "match": "\\.gdshader|\\.gdshaderinc$", + "cmd": "clang-format -i '${file}'" + } + ] + }, + "csharp.debug.justMyCode": false, + "csharp.debug.suppressJITOptimizations": true, + "csharp.semanticHighlighting.enabled": true, + "dotnet.backgroundAnalysis.analyzerDiagnosticsScope": "fullSolution", + "dotnet.backgroundAnalysis.compilerDiagnosticsScope": "fullSolution", + "dotnet.completion.showCompletionItemsFromUnimportedNamespaces": true, + "dotnet.enableXamlTools": false, + "dotnet.formatting.organizeImportsOnFormat": true, + "dotnet.preferCSharpExtension": true, + // "dotnet.server.useOmnisharp": true, // You decide + "dotnetAcquisitionExtension.enableTelemetry": false, + "editor.semanticHighlighting.enabled": true, + // C# doc comment colorization gets lost with semantic highlighting, but we + // need semantic highlighting for proper syntax highlighting with record + // shorthand. + // + // Here's a workaround for doc comment highlighting from + // https://github.com/OmniSharp/omnisharp-vscode/issues/3816 + "editor.tokenColorCustomizations": { + "[*]": { + // Themes that don't include the word "Dark" or "Light" in them. + // These are some bold colors that show up well against most dark and + // light themes. + // + // Change them to something that goes well with your preferred theme :) + "textMateRules": [ + { + "scope": "comment.documentation", + "settings": { + "foreground": "#0091ff" + } + }, + { + "scope": "comment.documentation.attribute", + "settings": { + "foreground": "#8480ff" + } + }, + { + "scope": "comment.documentation.cdata", + "settings": { + "foreground": "#0091ff" + } + }, + { + "scope": "comment.documentation.delimiter", + "settings": { + "foreground": "#aa00ff" + } + }, + { + "scope": "comment.documentation.name", + "settings": { + "foreground": "#ef0074" + } + } + ] + }, + "[*Dark*]": { + // Themes that include the word "Dark" in them. + "textMateRules": [ + { + "scope": "comment.documentation", + "settings": { + "foreground": "#608B4E" + } + }, + { + "scope": "comment.documentation.attribute", + "settings": { + "foreground": "#C8C8C8" + } + }, + { + "scope": "comment.documentation.cdata", + "settings": { + "foreground": "#E9D585" + } + }, + { + "scope": "comment.documentation.delimiter", + "settings": { + "foreground": "#808080" + } + }, + { + "scope": "comment.documentation.name", + "settings": { + "foreground": "#569CD6" + } + } + ] + }, + "[*Light*]": { + // Themes that include the word "Light" in them. + "textMateRules": [ + { + "scope": "comment.documentation", + "settings": { + "foreground": "#008000" + } + }, + { + "scope": "comment.documentation.attribute", + "settings": { + "foreground": "#282828" + } + }, + { + "scope": "comment.documentation.cdata", + "settings": { + "foreground": "#808080" + } + }, + { + "scope": "comment.documentation.delimiter", + "settings": { + "foreground": "#808080" + } + }, + { + "scope": "comment.documentation.name", + "settings": { + "foreground": "#808080" + } + } + ] + } + }, + "explorer.fileNesting.enabled": true, + "explorer.fileNesting.expand": false, + "explorer.fileNesting.patterns": { + "*": "$(capture).import, $(capture).uid" + }, + "markdownlint.config": { + // Allow non-unique heading names so we don't break the changelog. + "MD024": false, + // Allow html in markdown. + "MD033": false + }, + "markdownlint.ignore": [ + "**/LICENSE" + ], + "omnisharp.enableEditorConfigSupport": true, + "omnisharp.enableMsBuildLoadProjectsOnDemand": false, + "omnisharp.maxFindSymbolsItems": 3000, + "omnisharp.useModernNet": true, + // Remove these if you're happy with your terminal profiles. + "terminal.integrated.defaultProfile.windows": "Git Bash", + "terminal.integrated.profiles.windows": { + "Command Prompt": { + "icon": "terminal-cmd", + "path": [ + "${env:windir}\\Sysnative\\cmd.exe", + "${env:windir}\\System32\\cmd.exe" + ] + }, + "Git Bash": { + "icon": "terminal", + "source": "Git Bash" + }, + "PowerShell": { + "icon": "terminal-powershell", + "source": "PowerShell" + } + } +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..81798da --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,80 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "group": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + ], + "problemMatcher": "$msCompile", + "presentation": { + "echo": true, + "reveal": "silent", + "focus": false, + "panel": "shared", + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "build-without-tests", + "group": "build", + "command": "dotnet", + "type": "process", + "options": { + "env": { + "SKIP_TESTS": "1" + } + }, + "args": [ + "build", + ], + "problemMatcher": "$msCompile", + "presentation": { + "echo": true, + "reveal": "silent", + "focus": false, + "panel": "shared", + "showReuseMessage": false, + "clear": false + } + }, + { + "label": "coverage", + "group": "test", + "command": "${workspaceFolder}/coverage.sh", + "type": "shell", + "options": { + "cwd": "${workspaceFolder}" + }, + "presentation": { + "echo": true, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": false, + "clear": true + }, + }, + { + "label": "build-solutions", + "group": "test", + "command": "dotnet restore; ${env:GODOT} --headless --build-solutions --quit || exit 0", + "type": "shell", + "options": { + "cwd": "${workspaceFolder}" + }, + "presentation": { + "echo": true, + "reveal": "silent", + "focus": false, + "panel": "shared", + "showReuseMessage": false, + "clear": false + } + }, + ] +} diff --git a/.zed/settings.json b/.zed/settings.json new file mode 100644 index 0000000..9a3b6ef --- /dev/null +++ b/.zed/settings.json @@ -0,0 +1,44 @@ +// Zed settings +// +// For information on how to configure Zed, see the Zed +// documentation: https://zed.dev/docs/configuring-zed +// +// To see all of Zed's default settings without changing your +// custom settings, run `zed: open default settings` from the +// command palette (cmd-shift-p / ctrl-shift-p) +{ + "wrap_guides": [ + 80 + ], + "languages": { + "CSharp": { + "format_on_save": "on", + "formatter": "language_server", + // "code_actions_on_format": { + // "source.addMissingImports": true, + // "source.organizeImports": true, + // "source.fixAll": true + // }, + "tab_size": 2 + }, + "JSON": { + "format_on_save": "on", + "formatter": "language_server" + }, + "JSONC": { + "format_on_save": "on", + "formatter": "language_server" + } + }, + "telemetry": { + "diagnostics": false, + "metrics": false + }, + "file_types": { + "XML": [ + "csproj", + "xsl", + "props" + ] + } +} \ No newline at end of file diff --git a/ChickenGameTest.csproj b/ChickenGameTest.csproj new file mode 100644 index 0000000..4203716 --- /dev/null +++ b/ChickenGameTest.csproj @@ -0,0 +1,77 @@ + + + net10.0 + true + latest + enable + ChickenGameTest + + CS9057 + + + true + + + + portable + true + ChickenGameTest + 1.0.0 + ChickenGameTest + © 2024 SuperJrKing + SuperJrKing + SuperJrKing + + true + false + + + + true + $(DefineConstants);RUN_TESTS + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChickenGameTest.sln b/ChickenGameTest.sln new file mode 100644 index 0000000..b0fcd00 --- /dev/null +++ b/ChickenGameTest.sln @@ -0,0 +1,19 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ChickenGameTest", "ChickenGameTest.csproj", "{9E95E51D-5AD1-416E-B464-2C3F9C99BFED}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + ExportDebug|Any CPU = ExportDebug|Any CPU + ExportRelease|Any CPU = ExportRelease|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {9E95E51D-5AD1-416E-B464-2C3F9C99BFED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E95E51D-5AD1-416E-B464-2C3F9C99BFED}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E95E51D-5AD1-416E-B464-2C3F9C99BFED}.ExportDebug|Any CPU.ActiveCfg = ExportDebug|Any CPU + {9E95E51D-5AD1-416E-B464-2C3F9C99BFED}.ExportDebug|Any CPU.Build.0 = ExportDebug|Any CPU + {9E95E51D-5AD1-416E-B464-2C3F9C99BFED}.ExportRelease|Any CPU.ActiveCfg = ExportRelease|Any CPU + {9E95E51D-5AD1-416E-B464-2C3F9C99BFED}.ExportRelease|Any CPU.Build.0 = ExportRelease|Any CPU + EndGlobalSection +EndGlobal diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d12a5bd --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +# MIT License + +Copyright (c) 2024 SuperJrKing + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..3fe7134 --- /dev/null +++ b/README.md @@ -0,0 +1,222 @@ +# ChickenGameTest + +[![Chickensoft Badge][chickensoft-badge]][chickensoft-website] [![Discord][discord-badge]][discord] [![Read the docs][read-the-docs-badge]][docs] ![line coverage][line-coverage] ![branch coverage][branch-coverage] + +C# game template for Godot 4 with debug launch configurations, testing (locally and on CI/CD), code coverage, dependency update checks, and spell check working out-of-the-box! + +--- + +

+Cardboard Box with Chickensoft Logo +

+ +## 🥚 Getting Started + +This template allows you to easily create a C# game for Godot 4. Microsoft's `dotnet` tool allows you to easily create, install, and use templates. + +```sh +# Install this template +dotnet new install ChickenGameTest + +# Generate a new project based on this template +dotnet new chickengame --name "MyGameName" --param:author "My Name" + +cd MyGameName +dotnet build +``` + +## 💁 Getting Help + +*Is this template broken? Encountering obscure C# build problems?* We'll be happy to help you in the [Chickensoft Discord server][discord]. + +## 🏝 Environment Setup + +For the provided debug configurations and test coverage to work correctly, you must setup your development environment correctly. The [Chickensoft Setup Docs][setup-docs] describe how to setup your Godot and C# development environment, using Chickensoft's best practice recommendations. + +### VSCode Settings + +This template includes some Visual Studio Code settings in `.vscode/settings.json`. The settings facilitate terminal environments on Windows (Git Bash, PowerShell, Command Prompt) and macOS (zsh), as well as fixing some syntax colorization issues that Omnisharp suffers from. You'll also find settings that enable editor config support in Omnisharp and the .NET Roslyn analyzers for a more enjoyable coding experience. + +> Please double-check that the provided VSCode settings don't conflict with your existing settings. + +## .NET Versioning + +The included [`global.json`](./global.json) specifies the version of the .NET SDK and `Godot.NET.Sdk` that the game should use. Using a `global.json` file allows [Renovatebot] to provide your repository with automatic dependency update pull requests whenever a new version of [GodotSharp] is released. + +## 👷 Testing + +An example test is included in `test/src/GameTest.cs` that demonstrates how to write a test for your package using [GoDotTest] and [godot-test-driver]. + +> [GoDotTest] is an easy-to-use testing framework for Godot and C# that allows you to run tests from the command line, collect code coverage, and debug tests in VSCode. + +Tests run directly inside the game. The `.csproj` file is already pre-configured to prevent test scripts and test-only package dependencies from being included in release builds of your game! + +On CI/CD, software graphics drivers from [mesa] emulate a virtual graphics device for Godot to render to, allowing you to run visual tests in a headless environment. + +## 🏁 Application Entry Point + +The `Main.tscn` and `Main.cs` scene and script file are the entry point of your game. In general, you probably won't need to modify these unless you're doing something highly custom. + +If the game is running a release build, the `Main.cs` file will just immediately change the scene to `src/Game.tscn`. If the game is running in debug mode *and* GoDotTest has received the correct command line arguments to begin testing, the game will switch to the testing scene and hand off control to GoDotTest to run the game's tests. + +In general, prefer editing `src/Game.tscn` over `src/Main.tscn`. + +The provided debug configurations in `.vscode/launch.json` allow you to easily debug tests (or just the currently open test, provided its filename matches its class name). + +## 🚦 Test Coverage + +Code coverage requires a few `dotnet` global tools to be installed first. You should install these tools from the root of the project directory. + +The `nuget.config` file in the root of the project allows the correct version of `coverlet` to be installed from the coverlet nightly distributions. Overriding the coverlet version will be required [until coverlet releases a stable version with the fixes that allow it to work with Godot 4][coverlet-issues]. + +```sh +dotnet tool install --global coverlet.console +dotnet tool update --global coverlet.console +dotnet tool install --global dotnet-reportgenerator-globaltool +dotnet tool update --global dotnet-reportgenerator-globaltool +``` + +> Running `dotnet tool update` for the global tool is often necessary on Apple Silicon computers to ensure the tools are installed correctly. + +You can collect code coverage and generate coverage badges by running the bash script `coverage.sh` (on Windows, you can use the Git Bash shell that comes with git). + +```sh +# Must give coverage script permission to run the first time it is used. +chmod +x ./coverage.sh + +# Run code coverage: +./coverage.sh +``` + +You can also run test coverage through VSCode by opening the command palette and selecting `Tasks: Run Task` and then choosing `coverage`. + +If you are having trouble with `coverlet` finding your .NET runtime on Windows, you can use the PowerShell Script `coverage.ps1` instead. + +```ps +.\coverage.ps1 +``` + +## ⏯ Running the Project + +Several launch profiles are included for Visual Studio Code: + +- 🕹 **Debug Game** + + Runs the game in debug mode, allowing you to set breakpoints and inspect variables. + +- 🎭 **Debug Current Scene** + + Debugs the game and loads the scene with the **same name** and **in the same path** as the C# file that's actively selected in VSCode: e.g., a scene named `MyScene.tscn` must reside in the same directory as `MyScene.cs`, and you must have selected `MyScene.cs` as the active tab in VSCode before running the launch profile. + + If GoDotTest is able to find a `.tscn` file with the same name in the same location, it will run the game in debug mode and load the scene. + + > Naturally, Chickensoft recommends naming scenes after the C# script they use and keeping them in the same directory so that you can take advantage of this launch profile. + > + > ⚠️ It's very easy to rename a script class but forget to rename the scene file, or vice-versa. When that happens, this launch profile will pass in the *expected* name of the scene file based on the script's name, but Godot will fail to find a scene with that name since the script name and scene name are not the same. + +- 🧪 **Debug Tests** + + Runs the game in debug mode, specifying the command line flags needed by GoDotTest to run the tests. Debugging works the same as usual, allowing you to set breakpoints within the game's C# test files. + +- 🔬 **Debug Current Test** + + Debugs the game and loads the test class with the **same name** as the C# file that's actively selected in VSCode: e.g., a test file named `MyTest.cs` must contain a test class named `MyTest`, and you must have selected `MyTest.cs` as the active tab in VSCode before running the launch profile. + + > ⚠️ It's very easy to rename a test class but forget to rename the test file, or vice-versa. When that happens, this launch profile will pass in the name of the file but GoDotTest will fail to find a class with that name since the filename and class name are not the same. + +Note that each launch profile will trigger a build (see `./.vscode/tasks.json`) before debugging the game. + +> ⚠️ **Important:** You must setup a `GODOT` environment variable for the launch configurations above. If you haven't done so already, please see the [Chickensoft Setup Docs][setup-docs]. + +## 🏭 CI/CD + +This game includes various GitHub Actions workflows to help with development. + +## 🌈 Shaders + +You'll want to install [clang-format] and make sure it's available on your system `PATH` for automatic shader formatting to work whenever you save a `.gdshader` or `.gdshaderinc` file. + +- [Install on Windows](https://superuser.com/a/1611210) +- On macOS, use [homebrew] and run `brew install clang-format`. You'll want to make sure that homebrew is able to update your shell's `PATH` so that the installed `clang-format` binary is available globally. +- Linux: you know what to do + +> [!CAUTION] +> On Windows, you **must** logout and log back in (or restart your computer) after updating environment variables. On macOS/Linux, you may need to restart your application (if not logout and log back in) for the updated `PATH` to be recognized. + +This template includes an updated version of the `.clang-format` file mentioned in the [Godot Shaders Style Guide](https://docs.godotengine.org/en/stable/tutorials/shaders/shaders_style_guide.html#applying-formatting-automatically). + +For syntax highlighting, we recommend the [Godot Tools](https://marketplace.visualstudio.com/items?itemName=geequlim.godot-tools) extension since it provides functionality for other Godot engine features as well. + +### 🚥 Tests + +Tests run directly inside the GitHub runner machine (using [chickensoft-games/setup-godot]) on every push to the repository. If the tests fail to pass, the workflow will also fail to pass. + +You can configure which simulated graphics environments (`vulkan` and/or `opengl3`) you want to run tests on in [`.github/workflows/visual_tests.yaml`](.github/workflows/visual_tests.yaml). + +Currently, tests can only be run from the `ubuntu` runners. If you know how to make the workflow install mesa and a virtual window manager on macOS and Windows, we'd love to hear from you! + +Tests are executed by running the Godot test project in `ChickenGameTest` from the command line and passing in the relevant arguments to Godot so that [GoDotTest] can discover and run tests. + +### 🧑‍🏫 Spellcheck + +A spell check runs on every push to the repository. The spellcheck workflow settings can be configured in [`.github/workflows/spellcheck.yaml`](.github/workflows/spellcheck.yaml). + +The [Code Spell Checker][cspell] plugin for VSCode is recommended to help you catch typos before you commit them. If you need add a word to the dictionary or ignore a certain path, you can edit the project's `cspell.json` file. + +You can also words to the local `cspell.json` file from VSCode by hovering over a misspelled word and selecting `Quick Fix...` and then `Add "{word}" to config: cspell.json`. + +![Fix Spelling](docs/spelling_fix.png) + +### 🗂 Version Change + +The included workflow in [`.github/workflows/version_change.yaml`](.github/workflows/version_change.yaml) can be manually dispatched to open a pull request that replaces the version number in `ChickenGameTest.csproj` with the version you specify in the workflow's inputs. + +![Version Change Workflow](docs/version_change.png) + +### 📦 Publish to Nuget + +The included workflow in [`.github/workflows/publish.yaml`](.github/workflows/publish.yaml) can be manually dispatched when you're ready to publish your package to Nuget. + +> To publish to nuget, you need a repository or organization secret named `NUGET_API_KEY` that contains your Nuget API key. The `NUGET_API_KEY` must be a GitHub actions secret to keep it safe! + +![Publish Workflow](docs/publish.png) + +### 🏚 Renovatebot + +This repository includes a [`renovate.json`](./renovate.json) configuration for use with [Renovatebot]. Renovatebot can automatically open pull requests to help you keep your dependencies up to date when it detects new dependency versions have been released. Because Godot has such a rapid release cycle, automating dependency updates can be a huge time saver if you're trying to stay on the latest version of Godot. + +![Renovatebot Pull Request](docs/renovatebot_pr.png) + +> Unlike Dependabot, Renovatebot is able to combine all dependency updates into a single pull request — a must-have for Godot C# repositories where each sub-project needs the same Godot.NET.Sdk versions. If dependency version bumps were split across multiple repositories, the builds would fail in CI. + +The easiest way to add Renovatebot to your repository is to [install it from the GitHub Marketplace][get-renovatebot]. Note that you have to grant it access to each organization and repository you want it to monitor. + +The included `renovate.json` includes a few configuration options to limit how often Renovatebot can open pull requests as well as regex's to filter out some poorly versioned dependencies to prevent invalid dependency version updates. + +--- + +🐣 Package generated from a 🐤 Chickensoft Template — + + + + +[chickensoft-badge]: https://chickensoft.games/img/badges/chickensoft_badge.svg +[chickensoft-website]: https://chickensoft.games +[discord-badge]: https://chickensoft.games/img/badges/discord_badge.svg +[discord]: https://discord.gg/gSjaPgMmYW +[read-the-docs-badge]: https://chickensoft.games/img/badges/read_the_docs_badge.svg +[docs]: https://chickensoft.games/docs +[line-coverage]: badges/line_coverage.svg +[branch-coverage]: badges/branch_coverage.svg + + +[GoDotTest]: https://github.com/chickensoft-games/go_dot_test +[setup-docs]: https://chickensoft.games/docs/setup +[cspell]: https://marketplace.visualstudio.com/items?itemName=streetsidesoftware.code-spell-checker +[Renovatebot]: https://www.mend.io/free-developer-tools/renovate/ +[get-renovatebot]: https://github.com/apps/renovate +[godot-test-driver]: https://github.com/derkork/godot-test-driver +[coverlet-issues]: https://github.com/coverlet-coverage/coverlet/issues/1422 +[GodotSharp]: https://www.nuget.org/packages/GodotSharp/ +[chickensoft-games/setup-godot]: https://github.com/chickensoft-games/setup-godot +[homebrew]: https://brew.sh/ diff --git a/addons.jsonc b/addons.jsonc new file mode 100644 index 0000000..2925576 --- /dev/null +++ b/addons.jsonc @@ -0,0 +1,26 @@ +// Godot addons configuration file for use with the GodotEnv tool. +// See https://github.com/chickensoft-games/GodotEnv for more info. +// -------------------------------------------------------------------- // +// Note: this is a JSONC file, so you can use comments! +// If using Rider, see https://youtrack.jetbrains.com/issue/RIDER-41716 +// for any issues with JSONC. +// -------------------------------------------------------------------- // +{ + "$schema": "https://chickensoft.games/schemas/addons.schema.json", + // "path": "addons", // default + // "cache": ".addons", // default + "addons": { + "imrp": { // name must match the folder name in the repository + "url": "https://github.com/MakovWait/improved_resource_picker", + // "source": "remote", // default + // "checkout": "main", // default + "subfolder": "addons/imrp" + }, + "godot_debug_draw_3d": { + "url": "https://github.com/DmitriySalnikov/godot_debug_draw_3d/releases/download/1.7.3/debug-draw-3d_1.7.3.zip/", + "source": "zip", // optional — this is the default + // "checkout": "master", // optional — this is the default + // "subfolder": "addons/debug_draw_3d" // optional — defaults to "/" + }, + } +} diff --git a/addons/.editorconfig b/addons/.editorconfig new file mode 100644 index 0000000..d30b5ec --- /dev/null +++ b/addons/.editorconfig @@ -0,0 +1,12 @@ +# Editor configs in nested directories override those in parent directories +# for the directory in which they are placed. +# +# This editor config prevents the code editor from analyzing C# files which +# belong to addons. +# +# Ignoring C# addon scripts is generally preferable, since C# can be coded +# in a variety of ways that may or may not trigger warnings based on your +# own editorconfig or IDE settings. + +[*.cs] +generated_code = true \ No newline at end of file diff --git a/assets b/assets new file mode 160000 index 0000000..b5ad429 --- /dev/null +++ b/assets @@ -0,0 +1 @@ +Subproject commit b5ad429c59af10bc190d5a2d09c3790a8f4c3620 diff --git a/badges/.gdignore b/badges/.gdignore new file mode 100644 index 0000000..e69de29 diff --git a/badges/branch_coverage.svg b/badges/branch_coverage.svg new file mode 100644 index 0000000..069c951 --- /dev/null +++ b/badges/branch_coverage.svg @@ -0,0 +1,113 @@ + + + Code coverage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Generated by: ReportGenerator 5.1.17.0 + + + + Coverage + Coverage + + N/AN/A + + + + + + Branch coverage + + + \ No newline at end of file diff --git a/badges/line_coverage.svg b/badges/line_coverage.svg new file mode 100644 index 0000000..fcdc4e5 --- /dev/null +++ b/badges/line_coverage.svg @@ -0,0 +1,113 @@ + + + Code coverage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Generated by: ReportGenerator 5.1.17.0 + + + + Coverage + Coverage + 100%100% + + + + + + Line coverage + + + + \ No newline at end of file diff --git a/coverage.ps1 b/coverage.ps1 new file mode 100644 index 0000000..42dd72c --- /dev/null +++ b/coverage.ps1 @@ -0,0 +1,53 @@ +# To collect code coverage, you will need the following environment setup: +# +# - A "GODOT" environment variable pointing to the Godot executable +# - ReportGenerator installed +# +# dotnet tool install -g dotnet-reportgenerator-globaltool +# +# - A version of coverlet > 3.2.0. +# +# As of Jan 2023, this is not yet released. +# +# The included `nuget.config` file will allow you to install a nightly +# version of coverlet from the coverlet nightly nuget feed. +# +# dotnet tool install --global coverlet.console --prerelease. +# +# You can build coverlet yourself, but you will need to edit the path to +# coverlet below to point to your local build of the coverlet dll. +dotnet build --no-restore + +coverlet ` + "./.godot/mono/temp/bin/Debug" --verbosity detailed ` + --target $env:GODOT ` + --targetargs "--run-tests --coverage --quit-on-finish" ` + --format "opencover" ` + --output "./coverage/coverage.xml" ` + --exclude-by-file "**/test/**/*.cs" ` + --exclude-by-file "**/*Microsoft.NET.Test.Sdk.Program.cs" ` + --exclude-by-file "**/Godot.SourceGenerators/**/*.cs" ` + --exclude-assemblies-without-sources "missingall" + +# Projects included via will be collected in code coverage. +# If you want to exclude them, replace the string below with the names of +# the assemblies to ignore. e.g., +# $ASSEMBLIES_TO_REMOVE="-AssemblyToRemove1;-AssemblyToRemove2" +$ASSEMBLIES_TO_REMOVE="" + +reportgenerator ` + -reports:"./coverage/coverage.xml" ` + -targetdir:"./coverage/report" ` + "-assemblyfilters:$ASSEMBLIES_TO_REMOVE" ` + "-classfilters:-GodotPlugins.Game.Main;-ChickenGameTest.Main" ` + -reporttypes:"Html;Badges" + +# Copy badges into their own folder. The badges folder should be included in +# source control so that the README.md in the root can reference the badges. +If (!(Test-Path -Path "./badges")) { + New-Item -ItemType directory -Path "./badges" +} +Move-Item "./coverage/report/badge_branchcoverage.svg" "./badges/branch_coverage.svg" -Force +Move-Item "./coverage/report/badge_linecoverage.svg" "./badges/line_coverage.svg" -Force + +Invoke-Expression ("cmd /c start coverage/report/index.htm") diff --git a/coverage.sh b/coverage.sh new file mode 100644 index 0000000..2fb47a8 --- /dev/null +++ b/coverage.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +# To collect code coverage, you will need the following environment setup: +# +# - A "GODOT" environment variable pointing to the Godot executable +# - ReportGenerator installed +# +# dotnet tool install -g dotnet-reportgenerator-globaltool +# +# - A version of coverlet > 3.2.0. +# +# As of Jan 2023, this is not yet released. +# +# The included `nuget.config` file will allow you to install a nightly +# version of coverlet from the coverlet nightly nuget feed. +# +# dotnet tool install --global coverlet.console --prerelease. +# +# You can build coverlet yourself, but you will need to edit the path to +# coverlet below to point to your local build of the coverlet dll. +# +# If you need help with coverage, feel free to join the Chickensoft Discord. +# https://chickensoft.games + +dotnet build --no-restore + +coverlet \ + "./.godot/mono/temp/bin/Debug" --verbosity detailed \ + --target $GODOT \ + --targetargs "--run-tests --coverage --quit-on-finish" \ + --format "opencover" \ + --output "./coverage/coverage.xml" \ + --exclude-by-file "**/test/**/*.cs" \ + --exclude-by-file "**/*Microsoft.NET.Test.Sdk.Program.cs" \ + --exclude-by-file "**/Godot.SourceGenerators/**/*.cs" \ + --exclude-assemblies-without-sources "missingall" + +# Projects included via will be collected in code coverage. +# If you want to exclude them, replace the string below with the names of +# the assemblies to ignore. e.g., +# ASSEMBLIES_TO_REMOVE="-AssemblyToRemove1;-AssemblyToRemove2" +ASSEMBLIES_TO_REMOVE="" + +reportgenerator \ + -reports:"./coverage/coverage.xml" \ + -targetdir:"./coverage/report" \ + "-assemblyfilters:$ASSEMBLIES_TO_REMOVE" \ + "-classfilters:-GodotPlugins.Game.Main;-ChickenGameTest.Main" \ + -reporttypes:"Html;Badges" + +# Copy badges into their own folder. The badges folder should be included in +# source control so that the README.md in the root can reference the badges. + +mkdir -p ./badges +mv ./coverage/report/badge_branchcoverage.svg ./badges/branch_coverage.svg +mv ./coverage/report/badge_linecoverage.svg ./badges/line_coverage.svg + +# Determine OS, open coverage accordingly. + +case "$(uname -s)" in + + Darwin) + echo 'Mac OS X' + open coverage/report/index.htm + ;; + + Linux) + echo 'Linux' + ;; + + CYGWIN*|MINGW32*|MSYS*|MINGW*) + echo 'MS Windows' + start coverage/report/index.htm + ;; + + *) + echo 'Other OS' + ;; +esac diff --git a/coverage/.gdignore b/coverage/.gdignore new file mode 100644 index 0000000..e69de29 diff --git a/cspell.json b/cspell.json new file mode 100644 index 0000000..b6dfb63 --- /dev/null +++ b/cspell.json @@ -0,0 +1,103 @@ +{ + "files": [ + "**/*.*" + ], + "ignorePaths": [ + "**/*.tscn", + "**/*.import", + "badges/**/*.*", + "coverage/**/*.*", + ".godot/**/*.*", + "**/*.tres", + "**/*.uid", + "**/*.pdb", + "**/*.godot", + "**/*.dylib", + "**/*.dll", + "**/bin/**/*.*", + "**/obj/**/*.*", + "addons/**/*.*" + ], + "words": [ + "animatable", + "assemblyfilters", + "automerge", + "binutils", + "branchcoverage", + "camelcase", + "ccdik", + "chickenconfig", + "chickengame", + "Chickensoft", + "classfilters", + "Cmdline", + "cubemap", + "CYGWIN", + "Decompilation", + "devbuild", + "dtls", + "dylib", + "endregion", + "fabrik", + "framebuffer", + "gdshader", + "gdshaderinc", + "globaltool", + "gltf", + "godotengine", + "hmac", + "issuecomment", + "ITEMHALFSIZE", + "joypad", + "kisak", + "lcov", + "lightmap", + "lightmapper", + "lihop", + "linecoverage", + "methodcoverage", + "missingall", + "msbuild", + "MSYS", + "mult", + "multisample", + "nameof", + "netstandard", + "NOLOGO", + "nupkg", + "occluder", + "omni", + "Omnisharp", + "opencover", + "opengl", + "OPTOUT", + "paramref", + "pascalcase", + "randomizer", + "raymarch", + "renovatebot", + "reportgenerator", + "reporttypes", + "roslynator", + "Shouldly", + "spir", + "subfolders", + "targetargs", + "targetdir", + "theora", + "triplanar", + "tscn", + "tweener", + "typeof", + "typeparam", + "typeparamref", + "ulong", + "upnp", + "vorbis", + "voxel", + "vulkan", + "Wyri", + "XRIP", + "Xunit" + ] +} diff --git a/default_bus_layout.tres b/default_bus_layout.tres new file mode 100644 index 0000000..17d0032 --- /dev/null +++ b/default_bus_layout.tres @@ -0,0 +1,3 @@ +[gd_resource type="AudioBusLayout" format=3 uid="uid://bx2uo1td15816"] + +[resource] diff --git a/docs/.gdignore b/docs/.gdignore new file mode 100644 index 0000000..e69de29 diff --git a/docs/publish.png b/docs/publish.png new file mode 100644 index 0000000..dc06270 Binary files /dev/null and b/docs/publish.png differ diff --git a/docs/renovatebot_pr.png b/docs/renovatebot_pr.png new file mode 100644 index 0000000..13075ed Binary files /dev/null and b/docs/renovatebot_pr.png differ diff --git a/docs/spelling_fix.png b/docs/spelling_fix.png new file mode 100644 index 0000000..411a97b Binary files /dev/null and b/docs/spelling_fix.png differ diff --git a/docs/version_change.png b/docs/version_change.png new file mode 100644 index 0000000..6d13dcb Binary files /dev/null and b/docs/version_change.png differ diff --git a/global.json b/global.json new file mode 100644 index 0000000..36573b2 --- /dev/null +++ b/global.json @@ -0,0 +1,9 @@ +{ + "sdk": { + "version": "10.0.104", + "rollForward": "major" + }, + "msbuild-sdks": { + "Godot.NET.Sdk": "4.6.2" + } +} diff --git a/icon.png b/icon.png new file mode 100644 index 0000000..f7fe334 Binary files /dev/null and b/icon.png differ diff --git a/icon.png.import b/icon.png.import new file mode 100644 index 0000000..bb3b45f --- /dev/null +++ b/icon.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://d43sw60ib1iq" +path="res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://icon.png" +dest_files=["res://.godot/imported/icon.png-487276ed1e3a0c39cad0279d744ee560.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/nuget.config b/nuget.config new file mode 100644 index 0000000..d0c8e35 --- /dev/null +++ b/nuget.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/omnisharp.json b/omnisharp.json new file mode 100644 index 0000000..25cce1e --- /dev/null +++ b/omnisharp.json @@ -0,0 +1,20 @@ +{ + "RoslynExtensionsOptions": { + "enableAnalyzersSupport": true, + "enableDecompilationSupport": true, + "enableImportCompletion": true + }, + "FormattingOptions": { + "enableEditorConfigSupport": true, + "organizeImports": true, + "SeparateImportDirectiveGroups": true + }, + "RenameOptions": { + "renameInComments": true, + "renameOverloads": true, + "renameInStrings": true + }, + "SDK": { + "includePrereleases": true + } +} \ No newline at end of file diff --git a/project.godot b/project.godot new file mode 100644 index 0000000..32d062b --- /dev/null +++ b/project.godot @@ -0,0 +1,54 @@ +; 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=5 + +[animation] + +compatibility/default_parent_skeleton_in_mesh_instance_3d=true + +[application] + +config/name="ChickenGameTest" +run/main_scene="res://src/Main.tscn" +config/features=PackedStringArray("4.6", "C#", "Mobile") +config/icon="res://icon.png" + +[debug_draw_3d] + +settings/addon_root_folder="res://addons/debug_draw_3d" + +[display] + +window/size/viewport_width=720 +window/size/viewport_height=720 +window/size/initial_position_type=3 +window/size/window_width_override=1280 +window/size/window_height_override=720 + +[dotnet] + +project/assembly_name="ChickenGameTest" + +[editor] + +naming/scene_name_casing=1 + +[editor_plugins] + +enabled=PackedStringArray("res://addons/imrp/plugin.cfg") + +[gui] + +theme/default_font_multichannel_signed_distance_field=true +theme/default_font_generate_mipmaps=true +theme/default_theme_scale=2.0 + +[rendering] + +renderer/rendering_method="mobile" diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..0436e07 --- /dev/null +++ b/renovate.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["github>chickensoft-games/renovate:godot"] +} diff --git a/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs b/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs new file mode 100644 index 0000000..87087d7 --- /dev/null +++ b/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs @@ -0,0 +1,71 @@ +using ChickenGameTest; +using Godot; +using System; + +public partial class ConveyorBeltStraight : Node3D +{ + [Export] public Vector3I VoxelPos { get; set; } = default; + [Export] public int Width { get; set; } = default; + public GridTransform3D VoxelTransform + { + get => GridTransform3D.FromGodot(Transform); + set => Transform = value.ToGodot(); + } + public override void _Ready() + { + var node = GetNode("Node") as TestItemConveyor; + base._Ready(); + // GD.PrintS(VoxelTransform.Origin,VoxelTransform.Forward); + node.StartPort = node.CreatePort( + new(VoxelTransform.TranslatedLocal(new(1-Width,0,0)).RotateY(2), + // Direction.Back, + Width, + PortAccess.BiDirectional), + new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.Start), + new LaneSpan(0, 1)); + // GD.Print(VoxelTransform.Origin,GridTransform3D.FromGodot(Transform.TranslatedLocal(Vector3.Left)).Origin); + node.EndPort = node.CreatePort( + new(VoxelTransform, //new(GridTransform3D.FromGodot(Transform.TranslatedLocal(Vector3.Left)), + // Direction.Front, + Width, + PortAccess.BiDirectional), + new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.End), + new LaneSpan(0, 1)); + + // node.OtherPorts = [ + // node.CreatePort( + // new(VoxelTransform.RotateY90CW(), + // // Direction.Right, + // 1, + // PortAccess.In), + // new ItemConveyor.BeltTOffset(node.Length/2), + // LaneSpan.One), + // node.CreatePort( + // new(VoxelTransform.RotateY90CCW(), + // // Direction.Left, + // 1, + // PortAccess.In), + // new ItemConveyor.BeltTOffset(node.Length/2), + // LaneSpan.One) + // ]; + // new ConveyorPort(this, + // new BeltPortProfile(VoxelPos, Direction.Back, 1, PortAccess.BiDirectional), + // new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.Start), + // (item, offset) => + // { + // var obstacle = node.GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, 0, offset, LaneSpan.One); + // return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0); + // }, + // (item, offset) => { _items.Insert(0, new(item, offset)); return true;}); + // node.EndPort = new ConveyorPort(this, + // new BeltPortProfile(VoxelPos, Direction.Front, 1, PortAccess.BiDirectional), + // new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.End), + // (item, offset) => + // { + // var obstacle = node.GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, node.Length, offset, LaneSpan.One); + // return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0); + // // return ItemConveyor.ITEMSIZE < GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart, Length, SpeedMagnitude, LaneSpan.One); + // }, + // (item, offset) => { _items.Add(new(item, offset)); return true;}); + } +} diff --git a/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs.uid b/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs.uid new file mode 100644 index 0000000..5fd0dc8 --- /dev/null +++ b/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs.uid @@ -0,0 +1 @@ +uid://dgyuh6un1qoj4 diff --git a/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.tscn b/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.tscn new file mode 100644 index 0000000..32a8f1b --- /dev/null +++ b/src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.tscn @@ -0,0 +1,34 @@ +[gd_scene load_steps=7 format=3 uid="uid://c4h7mwnfrdesg"] + +[ext_resource type="Script" uid="uid://dgyuh6un1qoj4" path="res://src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs" id="1_mn8ro"] +[ext_resource type="Script" uid="uid://j24fuotdwwx4" path="res://src/VoxelGrid/TestItemConveyor.cs" id="2_1vr1d"] +[ext_resource type="PackedScene" uid="uid://bapdy1c6rb4qr" path="res://assets/kenney_conveyor-kit/Models/GLB format/conveyor-sides.glb" id="2_mn8ro"] +[ext_resource type="PackedScene" uid="uid://ccuhhy2oa8xvm" path="res://assets/kenney_conveyor-kit/Models/GLB format/arrow-basic.glb" id="3_1vr1d"] +[ext_resource type="Script" uid="uid://bjuntmf2sjynp" path="res://src/VoxelGrid/ConveyorItemRender.cs" id="4_g7n1f"] + +[sub_resource type="Curve3D" id="Curve3D_x5qdr"] +_data = { +"points": PackedVector3Array(0, 0, 0, 0, 0, 0, 0, 0.5, 0.5, 0, 0, 0, 0, 0, 0, 0, 0.5, -0.5), +"tilts": PackedFloat32Array(0, 0) +} +point_count = 2 + +[node name="ConveyorBeltStraight" type="Node3D"] +script = ExtResource("1_mn8ro") + +[node name="Node" type="Node" parent="."] +script = ExtResource("2_1vr1d") + +[node name="conveyor-sides" parent="." instance=ExtResource("2_mn8ro")] +transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 0, 0, 0) + +[node name="arrow-basic2" parent="." instance=ExtResource("3_1vr1d")] +transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 0, 0.21879572, 0) + +[node name="Path3D" type="Path3D" parent="."] +curve = SubResource("Curve3D_x5qdr") + +[node name="Node2" type="Node" parent="." node_paths=PackedStringArray("Path3D", "ItemConveyor")] +script = ExtResource("4_g7n1f") +Path3D = NodePath("../Path3D") +ItemConveyor = NodePath("../Node") diff --git a/src/ESCThing/StructuralInstance.Builder.cs b/src/ESCThing/StructuralInstance.Builder.cs new file mode 100644 index 0000000..f96680d --- /dev/null +++ b/src/ESCThing/StructuralInstance.Builder.cs @@ -0,0 +1,133 @@ +namespace ChickenGameTest; + +using System; +using System.Collections.Generic; + +// public partial class StructuralInstance where TAttribute : class +// { +public sealed class StructuralBuilder where TAttribute : class +{ + + private readonly Dictionary _components = []; + + public StructuralBuilder() { } + + public StructuralBuilder(IStructuralInstance existing) + { + foreach (var (id, value) in existing.GetAttributes()) + { + _components[id] = value; + } + // for (int i = 0; i < existing.ComponentCount; i++) + // { + // var typeId = existing._attributesTypeIds[i]; + // var component = existing.GetComponentAt(i); + + // _components[typeId] = component; + // } + } + + public StructuralBuilder Add(T component) where T : TAttribute + { + int id = ComponentTypeRegistry.GetId(); + _components[id] = component; + return this; + } + public StructuralBuilder Upsert(Func ifExists, Func none) where T : TAttribute + { + int id = ComponentTypeRegistry.GetId(); + _components[id] = _components.TryGetValue(id, out var old) ? ifExists((T)old) : none(); + return this; + } + public StructuralBuilder CombineWith(IStructuralInstance other, Action? configure = null) + { + var binder = new CombineBinder(); + configure?.Invoke(binder); + foreach (var (id, value) in other.GetAttributes()) + { + if (binder._ignores.Contains(id)) + { + continue; + } + if (_components.TryGetValue(id, out var existing) + && binder._rules.TryGetValue(id, out var rule)) + { + _components[id] = rule.Invoke(existing, value); + continue; + } + + _components[id] = value; + + } + + return this; + } + + public StructuralBuilder Remove() where T : TAttribute + { + int id = ComponentTypeRegistry.GetId(); + _components.Remove(id); + return this; + } + + public bool Has() where T : TAttribute + { + int id = ComponentTypeRegistry.GetId(); + return _components.ContainsKey(id); + } + + public StructuralInstance Build() + { + int count = _components.Count; + + var typeIds = new int[count]; + var components = new TAttribute[count]; + + int i = 0; + foreach (var kv in _components) + { + typeIds[i] = kv.Key; + components[i] = kv.Value; + i++; + } + + Array.Sort(typeIds, components); + return new StructuralInstance(typeIds, components); + } + public MutableStructuralInstance BuildMutable() + { + // int count = _components.Count; + + var sortedList = new SortedList(_components); + // var typeIds = new int[count]; + // var components = new TAttribute[count]; + + // int i = 0; + // foreach (var kv in _components) + // { + // typeIds[i] = kv.Key; + // components[i] = kv.Value; + // i++; + // } + + // Array.Sort(typeIds, components); + return new MutableStructuralInstance(sortedList); + } + public class CombineBinder + { + internal readonly Dictionary> _rules = []; + internal readonly HashSet _ignores = []; + public CombineBinder Bind(Func func) where T : TAttribute + { + _rules[ComponentType.Id] = (a, b) => func((T)a, (T)b); + return this; + } + public CombineBinder Ignore() where T : TAttribute + { + _ignores.Add(ComponentType.Id); + return this; + } + } +} + +// } diff --git a/src/ESCThing/StructuralInstance.Builder.cs.uid b/src/ESCThing/StructuralInstance.Builder.cs.uid new file mode 100644 index 0000000..bbed3ad --- /dev/null +++ b/src/ESCThing/StructuralInstance.Builder.cs.uid @@ -0,0 +1 @@ +uid://bk721rnhegl0x diff --git a/src/ESCThing/StructuralInstance.cs b/src/ESCThing/StructuralInstance.cs new file mode 100644 index 0000000..a353dc1 --- /dev/null +++ b/src/ESCThing/StructuralInstance.cs @@ -0,0 +1,255 @@ +namespace ChickenGameTest; + +using System; +using System.Collections.Generic; +using System.Linq; +using Chickensoft.Sync.Primitives; +using SJK.Functional; + +public interface IStructuralInstance : IEquatable> +{ + int ComponentCount { get; } + bool Has() where T : TAttribute; + Option Get() where T : TAttribute; + // TAttribute GetComponentAt(int index); + IEnumerable<(int Id, TAttribute Value)> GetAttributes(); + bool IEquatable>.Equals(IStructuralInstance? other) => Enumerable.SequenceEqual(GetAttributes(), other.GetAttributes(), EqualityComparer<(int, TAttribute)>.Default); + + int ComputeHashCode() + { + HashCode hash = new HashCode(); + foreach (var item in GetAttributes()) + { + hash.Add(item); + } + return hash.ToHashCode(); + } +} + +public sealed partial class StructuralInstance : IStructuralInstance, IEquatable> where TAttribute : class +{ + private readonly int _hashCode; + internal readonly int[] _attributesTypeIds; + internal readonly TAttribute[] _attributes; + + public int ComponentCount => _attributes.Length; + + public TAttribute GetComponentAt(int index) => _attributes[index]; + internal StructuralInstance(int[] componentTypeIds, TAttribute[] attributes) + { + _attributesTypeIds = componentTypeIds; + _attributes = attributes; + _hashCode = (this as IStructuralInstance).ComputeHashCode(); + } + + public bool Has() where T : TAttribute + { + // int typeId = ComponentTypeRegistry.GetId(); + var typeId = ComponentType.Id; + return Array.BinarySearch(_attributesTypeIds, typeId) >= 0; + } + + public Option Get() where T : TAttribute + { + // int typeId = ComponentTypeRegistry.GetId(); + var typeId = ComponentType.Id; + int index = Array.BinarySearch(_attributesTypeIds, typeId); + + if (index < 0) + { + return Option.None; + } + + return Option.Some((T)_attributes[index]); + } + + public bool Equals(StructuralInstance? other) + { + if (ReferenceEquals(this, other)) + { + return true; + } + if (other is null) + { + return false; + } + return _hashCode == other._hashCode && Enumerable.SequenceEqual(_attributes, other._attributes, EqualityComparer.Default);// && AttributesAreEqual(other); + } + public override bool Equals(object? obj) => obj is StructuralInstance value? Equals(value) : obj is IStructuralInstance v && v.Equals(this); + + public override int GetHashCode() => _hashCode; + public IEnumerable<(int Id, TAttribute Value)> GetAttributes() + { + for (int i = 0; i < ComponentCount; i++) + { + yield return (_attributesTypeIds[i], _attributes[i]); + } + } + public override string ToString() => base.ToString(); +} +public sealed class MutableStructuralInstance : IStructuralInstance, IEquatable> where TAttribute : class +{ + private readonly SortedList _attributes = []; + public int ComponentCount => _attributes.Count; + public MutableStructuralInstance(SortedList attributes) + { + _attributes = attributes; + } + public Option Get() where T : TAttribute => _attributes.TryGetValue(ComponentType.Id, out var attribute) ? Option.Some((T)attribute) : Option.None; + public IEnumerable<(int Id, TAttribute Value)> GetAttributes() => _attributes.Select(x => (x.Key, x.Value)); + public TAttribute GetComponentAt(int index) => _attributes[index]; + public bool Has() where T : TAttribute => _attributes.ContainsKey(ComponentType.Id); + public void Set(T value) where T : TAttribute => _attributes[ComponentType.Id] = value; + public override int GetHashCode() => (this as IStructuralInstance).ComputeHashCode(); + public bool Equals(MutableStructuralInstance? other) => other is not null && Enumerable.SequenceEqual(_attributes, other._attributes, EqualityComparer>.Default); + public override bool Equals(object? obj) => obj is MutableStructuralInstance other ? Equals(other) : obj is IStructuralInstance v && v.Equals(this); +} + +public static class ComponentTypeRegistry +{ + private static readonly Dictionary _typeToId = []; + private static readonly List _idToType = []; + + public static int GetId(Type type) + { + if (_typeToId.TryGetValue(type, out var id)) + { + return id; + } + + id = _idToType.Count; + _typeToId[type] = id; + _idToType.Add(type); + + return id; + } + public static int GetId() => GetId(typeof(T)); +} +public static class ComponentType +{ + public static readonly int Id = ComponentTypeRegistry.GetId(); + public static readonly bool CacheTransitions = Resolve(); + + private static bool Resolve() + { + var attr = typeof(T).GetCustomAttributes(typeof(ComponentOptionsAttribute), false); + return attr.OfType().FirstOrNone().Map(static f => f.CacheTransitions).Or(true); + } +} +[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] +public sealed class ComponentOptionsAttribute : Attribute +{ + public bool CacheTransitions { get; init; } = true; +} +public interface IStructureRegistry where TAttribute : class +{ + StructuralInstance Canonicalize(StructuralInstance value); + StructuralInstance AddOrReplaceAttribute(StructuralInstance instance, T attribute) where T : TAttribute; + StructuralInstance RemoveAttribute(StructuralInstance instance) where T : TAttribute; +} +public class StructuralInstanceManger : IStructureRegistry where TAttribute : class +{ + private readonly HashSet> _instances = []; + public StructuralInstance Add(StructuralInstance instance, T component) where T : TAttribute + { + var builder = new StructuralBuilder(instance).Add(component); + return Canonicalize(builder.Build()); + + } + public StructuralInstance Canonicalize(StructuralInstance value) + { + + if (_instances.TryGetValue(value, out var id)) + { + return id; + } + _instances.Add(value); + return value; + } + public StructuralInstance AddOrReplaceAttribute(StructuralInstance instance, T attribute) where T : TAttribute + { + if (!graph.TryGetValue(instance, out var results)) + { + graph[instance] = results = []; + } + for (int i = 0; i < results.Count; i++) + { + if (results[i] is AddTransitionEntry addTransitionEntry && EqualityComparer.Default.Equals(addTransitionEntry.Value, attribute)) + { + return addTransitionEntry.Result; + } + } + var result =new AddTransitionEntry(ComponentType.Id, attribute, Canonicalize(new StructuralBuilder(instance).Add(attribute).Build())); + if (ComponentType.CacheTransitions) + { + results.Add(result); + } + return result.Result; + } + public StructuralInstance RemoveAttribute(StructuralInstance instance) where T : TAttribute + { + if (!graph.TryGetValue(instance, out var results)) + { + graph[instance] = results = []; + } + var id = ComponentType.Id; + for (int i = 0; i < results.Count; i++) + { + if (results[i] is RemoveTransitionEntry removeTransitionEntry && removeTransitionEntry.Id == id) + { + return removeTransitionEntry.Result; + } + } + var result =new RemoveTransitionEntry(id, Canonicalize(new StructuralBuilder(instance).Remove().Build())); + if (ComponentType.CacheTransitions) + { + results.Add(result); + } + return result.Result; + } + private Dictionary, List> graph = []; + + private record TransitionEntry(int Id); + + private record AddTransitionEntry(int Id, T Value, StructuralInstance Result) : TransitionEntry(Id) where T : TAttribute; + + private record RemoveTransitionEntry(int Id, StructuralInstance Result) : TransitionEntry(Id); + // private record ModifyTransitionEntry(int Id, TAttribute Value, StructuralInstance Result) : TransitionEntry(Id) where T : TAttribute; + +} +public class AutoStructureInstance where TAttribute : class +{ + private StructuralInstance _structuralInstance; + private IStructureRegistry _registry; + private Dictionary> _bindings = []; + + public void Set(T? value) where T : TAttribute + { + var old = _structuralInstance.Get(); + if (old.HasValue && old.Value.Equals(value)) + { + return; + } + if (value is null) + { + _structuralInstance = _registry.RemoveAttribute(_structuralInstance); + } + else + { + _structuralInstance = _registry.AddOrReplaceAttribute(_structuralInstance, value); + } + _bindings[ComponentType.Id].ForEach(item => item.DynamicInvoke(old,value)); + + } + public void Bind(Action callback) where T : TAttribute + { + int id = ComponentType.Id; + + if (!_bindings.TryGetValue(id, out var list)) + { + _bindings[id] = list = []; + } + + list.Add(callback); + } +} diff --git a/src/ESCThing/StructuralInstance.cs.uid b/src/ESCThing/StructuralInstance.cs.uid new file mode 100644 index 0000000..114beb0 --- /dev/null +++ b/src/ESCThing/StructuralInstance.cs.uid @@ -0,0 +1 @@ +uid://cexmjk01dgtbe diff --git a/src/ESCThing/TestStructural.cs b/src/ESCThing/TestStructural.cs new file mode 100644 index 0000000..e8305c4 --- /dev/null +++ b/src/ESCThing/TestStructural.cs @@ -0,0 +1,69 @@ +namespace ChickenGameTest; + +using System.Diagnostics; +using Godot; +using SJK.Functional; + +public class TestStructural +{ + public static void Test() + { + var a = new StructuralBuilder() + .Add(new Vector3(0,0,0)) + .Add(new Vector2(0,5)) + .Add(new Vector4(0,0,0,0)) + .Add(new Aabb()) + .Add(new int()) + .Add(new float()) + .Add(new Node()) + .Add(new Node()) + .Add(new Node2D()) + .Add("") + .Add(new test(){a = 5, b = "gg"}) + .Build(); + var registy = new StructuralInstanceManger(); + var b = registy.Add(a,new Vector2(1,2)); + var c = registy.Add(a,new Vector2(1,2)); + var d = registy.Canonicalize(new StructuralBuilder(a).Add(new Vector2(1,2)).Build()); + var g = new StructuralBuilder(a).Add(Option.Some(5)).Build(); + var h = new StructuralBuilder(a).Add(Option.Some(5)).BuildMutable(); + GD.Print(g.Equals(h)); + GD.Print(ReferenceEquals(b,c)); + GD.Print(ReferenceEquals(b,d)); + GD.Print(ComponentType.Id); + GD.Print(ComponentType.Id); + + var help = new StructuralBuilder(a) + .CombineWith(b, static configure => configure + .Bind(static (a, b) => a + b) + .Ignore()) + .Add(Vector2I.Zero) + .Build(); + GD.Print(help.Get().Map(i=>$"{i}").OrDefault("none")); + var timer = new Stopwatch(); + timer.Start(); + for (int i = 0; i < 100000; i++) + { + var e = registy.AddOrReplaceAttribute(a,new test()); + } + timer.Stop(); + GD.Print(timer.Elapsed); + timer.Reset(); + timer.Start(); + for (int i = 0; i < 100000; i++) + { + var e = registy.Canonicalize(new StructuralBuilder(a).Add(new test()).Build()); + } + timer.Stop(); + GD.Print(timer.Elapsed); + + + + } +} +[ComponentOptions(CacheTransitions = true)] +record test +{ + public int a; + public string b; +} diff --git a/src/ESCThing/TestStructural.cs.uid b/src/ESCThing/TestStructural.cs.uid new file mode 100644 index 0000000..c19a121 --- /dev/null +++ b/src/ESCThing/TestStructural.cs.uid @@ -0,0 +1 @@ +uid://br6rfqtryq0cx diff --git a/src/Game.cs b/src/Game.cs new file mode 100644 index 0000000..644e426 --- /dev/null +++ b/src/Game.cs @@ -0,0 +1,15 @@ +namespace ChickenGameTest; + +using Godot; + +public partial class Game : Control +{ + public Button TestButton { get; private set; } = default!; + public int ButtonPresses { get; private set; } + + public override void _Ready() + => TestButton = GetNode