init
This commit is contained in:
17
.clang-format
Normal file
17
.clang-format
Normal file
@@ -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
|
||||
415
.editorconfig
Normal file
415
.editorconfig
Normal file
@@ -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
|
||||
40
.gitattributes
vendored
Normal file
40
.gitattributes
vendored
Normal file
@@ -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
|
||||
30
.github/workflows/spellcheck.yaml
vendored
Normal file
30
.github/workflows/spellcheck.yaml
vendored
Normal file
@@ -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: "."
|
||||
35
.github/workflows/version_change.yaml
vendored
Normal file
35
.github/workflows/version_change.yaml
vendored
Normal file
@@ -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 }}.
|
||||
87
.github/workflows/visual_tests.yaml
vendored
Normal file
87
.github/workflows/visual_tests.yaml
vendored
Normal file
@@ -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."
|
||||
17
.gitignore
vendored
Normal file
17
.gitignore
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
nupkg/
|
||||
|
||||
coverage/*
|
||||
!coverage/.gdignore
|
||||
|
||||
.godot/
|
||||
bin/
|
||||
obj/
|
||||
.generated/
|
||||
.vs/
|
||||
.DS_Store
|
||||
|
||||
*.old
|
||||
*.translation
|
||||
addons/*
|
||||
!addons/.editorconfig
|
||||
.addons/*
|
||||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
[submodule "assets"]
|
||||
path = assets
|
||||
url = http://192.168.1.4:3000/Ronnie/FoodFactoryAssets
|
||||
115
.vscode/chickensoft.code-snippets
vendored
Normal file
115
.vscode/chickensoft.code-snippets
vendored
Normal file
@@ -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<State>;",
|
||||
"}",
|
||||
""
|
||||
],
|
||||
"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"
|
||||
}
|
||||
}
|
||||
29
.vscode/extensions.json
vendored
Normal file
29
.vscode/extensions.json
vendored
Normal file
@@ -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",
|
||||
]
|
||||
}
|
||||
34
.vscode/godot.code-snippets
vendored
Normal file
34
.vscode/godot.code-snippets
vendored
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
107
.vscode/launch.json
vendored
Normal file
107
.vscode/launch.json
vendored
Normal file
@@ -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"
|
||||
},
|
||||
]
|
||||
}
|
||||
187
.vscode/settings.json
vendored
Normal file
187
.vscode/settings.json
vendored
Normal file
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
80
.vscode/tasks.json
vendored
Normal file
80
.vscode/tasks.json
vendored
Normal file
@@ -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
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
44
.zed/settings.json
Normal file
44
.zed/settings.json
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
77
ChickenGameTest.csproj
Normal file
77
ChickenGameTest.csproj
Normal file
@@ -0,0 +1,77 @@
|
||||
<Project Sdk="Godot.NET.Sdk/4.6.2">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<EnableDynamicLoading>true</EnableDynamicLoading>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<RootNamespace>ChickenGameTest</RootNamespace>
|
||||
<!-- Catch compiler-mismatch issues with the Introspection generator as early as possible -->
|
||||
<WarningsAsErrors>CS9057</WarningsAsErrors>
|
||||
<!-- Required for some nuget packages to work -->
|
||||
<!-- godotengine/godot/issues/42271#issuecomment-751423827 -->
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<!-- To show generated files -->
|
||||
<!-- <EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles> -->
|
||||
<!--
|
||||
<CompilerGeneratedFilesOutputPath>.generated</CompilerGeneratedFilesOutputPath>
|
||||
-->
|
||||
<DebugType>portable</DebugType>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<Title>ChickenGameTest</Title>
|
||||
<Version>1.0.0</Version>
|
||||
<Description>ChickenGameTest</Description>
|
||||
<Copyright>© 2024 SuperJrKing</Copyright>
|
||||
<Authors>SuperJrKing</Authors>
|
||||
<Company>SuperJrKing</Company>
|
||||
|
||||
<SkipTests Condition="'$(SKIP_TESTS)' != ''">true</SkipTests>
|
||||
<RunTests>false</RunTests>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="
 ('$(Configuration)' == 'Debug' or '$(Configuration)' == 'ExportDebug')
 and '$(SkipTests)' != 'true' ">
|
||||
<RunTests>true</RunTests>
|
||||
<DefineConstants>$(DefineConstants);RUN_TESTS</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<!-- Production dependencies go here! -->
|
||||
<PackageReference Include="Chickensoft.GameTools" Version="3.1.6" />
|
||||
<PackageReference Include="SjkScripts" Version="1.0.1" />
|
||||
<PackageReference Include="System.IO.Abstractions" Version="22.1.0" />
|
||||
<PackageReference Include="EnvironmentAbstractions" Version="5.0.0" />
|
||||
<PackageReference Include="GodotSharp.SourceGenerators" Version="2.6.0" PrivateAssets="all" OutputItemType="analyzer" />
|
||||
<PackageReference Include="Chickensoft.SaveFileBuilder" Version="1.3.54" />
|
||||
<PackageReference Include="Chickensoft.AutoInject" Version="2.9.18" PrivateAssets="all" />
|
||||
<PackageReference Include="Chickensoft.Collections" Version="3.1.4" />
|
||||
<PackageReference Include="Chickensoft.GodotNodeInterfaces" Version="2.4.57" />
|
||||
<PackageReference Include="Chickensoft.Introspection" Version="3.0.2" />
|
||||
<PackageReference Include="Chickensoft.Introspection.Generator" Version="3.0.2" PrivateAssets="all" OutputItemType="analyzer" />
|
||||
<PackageReference Include="Chickensoft.Serialization" Version="3.1.0" />
|
||||
<PackageReference Include="Chickensoft.Serialization.Godot" Version="0.8.46" />
|
||||
<PackageReference Include="Chickensoft.LogicBlocks" Version="5.20.0" />
|
||||
<PackageReference Include="Chickensoft.LogicBlocks.DiagramGenerator" Version="5.20.0" PrivateAssets="all" OutputItemType="analyzer" />
|
||||
<PackageReference Include="Chickensoft.UMLGenerator" Version="1.1.0" />
|
||||
<PackageReference Include="Chickensoft.Sync" Version="2.2.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(RunTests)' == 'true'">
|
||||
<!-- Test dependencies go here! -->
|
||||
<!-- Dependencies added here will not be included in release builds. -->
|
||||
<PackageReference Include="Chickensoft.GoDotTest" Version="2.0.27" />
|
||||
<!-- Used to drive test scenes when testing visual code -->
|
||||
<PackageReference Include="Chickensoft.GodotTestDriver" Version="3.1.56" />
|
||||
<!-- Bring your own assertion library for tests! -->
|
||||
<!-- We're using Shouldly for this example, but you can use anything. -->
|
||||
<PackageReference Include="Shouldly" Version="4.3.0" />
|
||||
<!-- LightMock is a mocking library that works without reflection. -->
|
||||
<PackageReference Include="LightMock.Generator" Version="1.2.3" />
|
||||
<!-- LightMoq is a Chickensoft package which makes it more like Moq. -->
|
||||
<PackageReference Include="LightMoq" Version="0.1.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(RunTests)' != 'true'">
|
||||
<Compile Remove="test/**/*.cs" />
|
||||
<None Remove="test/**/*" />
|
||||
<EmbeddedResource Remove="test/**/*" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
19
ChickenGameTest.sln
Normal file
19
ChickenGameTest.sln
Normal file
@@ -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
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -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.
|
||||
222
README.md
Normal file
222
README.md
Normal file
@@ -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!
|
||||
|
||||
---
|
||||
|
||||
<p align="center">
|
||||
<img alt="Cardboard Box with Chickensoft Logo" src="icon.png" width="200">
|
||||
</p>
|
||||
|
||||
## 🥚 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`.
|
||||
|
||||

|
||||
|
||||
### 🗂 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.
|
||||
|
||||

|
||||
|
||||
### 📦 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!
|
||||
|
||||

|
||||
|
||||
### 🏚 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.
|
||||
|
||||

|
||||
|
||||
> 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 — <https://chickensoft.games>
|
||||
|
||||
<!-- Links -->
|
||||
|
||||
<!-- Header -->
|
||||
[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
|
||||
|
||||
<!-- Article -->
|
||||
[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/
|
||||
26
addons.jsonc
Normal file
26
addons.jsonc
Normal file
@@ -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 "/"
|
||||
},
|
||||
}
|
||||
}
|
||||
12
addons/.editorconfig
Normal file
12
addons/.editorconfig
Normal file
@@ -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
|
||||
1
assets
Submodule
1
assets
Submodule
Submodule assets added at b5ad429c59
0
badges/.gdignore
Normal file
0
badges/.gdignore
Normal file
113
badges/branch_coverage.svg
Normal file
113
badges/branch_coverage.svg
Normal file
@@ -0,0 +1,113 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="155" height="20">
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
@keyframes fade1 {
|
||||
0% { visibility: visible; opacity: 1; }
|
||||
27% { visibility: visible; opacity: 1; }
|
||||
33% { visibility: hidden; opacity: 0; }
|
||||
60% { visibility: hidden; opacity: 0; }
|
||||
66% { visibility: hidden; opacity: 0; }
|
||||
93% { visibility: hidden; opacity: 0; }
|
||||
100% { visibility: visible; opacity: 1; }
|
||||
}
|
||||
@keyframes fade2 {
|
||||
0% { visibility: hidden; opacity: 0; }
|
||||
27% { visibility: hidden; opacity: 0; }
|
||||
33% { visibility: visible; opacity: 1; }
|
||||
60% { visibility: visible; opacity: 1; }
|
||||
66% { visibility: hidden; opacity: 0; }
|
||||
93% { visibility: hidden; opacity: 0; }
|
||||
100% { visibility: hidden; opacity: 0; }
|
||||
}
|
||||
@keyframes fade3 {
|
||||
0% { visibility: hidden; opacity: 0; }
|
||||
27% { visibility: hidden; opacity: 0; }
|
||||
33% { visibility: hidden; opacity: 0; }
|
||||
60% { visibility: hidden; opacity: 0; }
|
||||
66% { visibility: visible; opacity: 1; }
|
||||
93% { visibility: visible; opacity: 1; }
|
||||
100% { visibility: hidden; opacity: 0; }
|
||||
}
|
||||
.linecoverage {
|
||||
animation-duration: 15s;
|
||||
animation-name: fade1;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
.branchcoverage {
|
||||
animation-duration: 15s;
|
||||
animation-name: fade2;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
.methodcoverage {
|
||||
animation-duration: 15s;
|
||||
animation-name: fade3;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
]]>
|
||||
</style>
|
||||
<title>Code coverage</title>
|
||||
<defs>
|
||||
<linearGradient id="gradient" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
||||
<stop offset="1" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="c">
|
||||
<stop offset="0" stop-color="#d40000"/>
|
||||
<stop offset="1" stop-color="#ff2a2a"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="a">
|
||||
<stop offset="0" stop-color="#e0e0de"/>
|
||||
<stop offset="1" stop-color="#fff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b">
|
||||
<stop offset="0" stop-color="#37c837"/>
|
||||
<stop offset="1" stop-color="#217821"/>
|
||||
</linearGradient>
|
||||
<linearGradient xlink:href="#a" id="e" x1="106.44" x2="69.96" y1="-11.96" y2="-46.84" gradientTransform="matrix(-.8426 -.00045 -.00045 -.8426 -94.27 -75.82)" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient xlink:href="#b" id="f" x1="56.19" x2="77.97" y1="-23.45" y2="10.62" gradientTransform="matrix(.8426 .00045 .00045 .8426 94.27 75.82)" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient xlink:href="#c" id="g" x1="79.98" x2="132.9" y1="10.79" y2="10.79" gradientTransform="matrix(.8426 .00045 .00045 .8426 94.27 75.82)" gradientUnits="userSpaceOnUse"/>
|
||||
|
||||
<mask id="mask">
|
||||
<rect width="155" height="20" rx="3" fill="#fff"/>
|
||||
</mask>
|
||||
|
||||
<g id="icon" transform="matrix(.04486 0 0 .04481 -.48 -.63)">
|
||||
<rect width="52.92" height="52.92" x="-109.72" y="-27.13" fill="url(#e)" transform="rotate(-135)"/>
|
||||
<rect width="52.92" height="52.92" x="70.19" y="-39.18" fill="url(#f)" transform="rotate(45)"/>
|
||||
<rect width="52.92" height="52.92" x="80.05" y="-15.74" fill="url(#g)" transform="rotate(45)"/>
|
||||
</g>
|
||||
</defs>
|
||||
|
||||
<g mask="url(#mask)">
|
||||
<rect x="0" y="0" width="90" height="20" fill="#444"/>
|
||||
<rect x="90" y="0" width="20" height="20" fill="#c00"/>
|
||||
<rect x="110" y="0" width="45" height="20" fill="#00B600"/>
|
||||
<rect x="0" y="0" width="155" height="20" fill="url(#gradient)"/>
|
||||
</g>
|
||||
|
||||
<g>
|
||||
|
||||
<path class="" fill="#fff" d="m 97.627847,15.246584 q 0,-0.36435 -0.255043,-0.619412 -0.255042,-0.254975 -0.619388,-0.254975 -0.364346,0 -0.619389,0.254975 -0.255042,0.255062 -0.255042,0.619412 0,0.36435 0.255042,0.619412 0.255043,0.254975 0.619389,0.254975 0.364346,0 0.619388,-0.254975 0.255043,-0.255062 0.255043,-0.619412 z m 0,-10.4931686 q 0,-0.3643498 -0.255043,-0.6194121 -0.255042,-0.2550624 -0.619388,-0.2550624 -0.364346,0 -0.619389,0.2550624 -0.255042,0.2550623 -0.255042,0.6194121 0,0.3643498 0.255042,0.6193246 0.255043,0.2551499 0.619389,0.2551499 0.364346,0 0.619388,-0.2551499 0.255043,-0.2549748 0.255043,-0.6193246 z m 5.829537,1.1659368 q 0,-0.3643498 -0.255042,-0.6194121 -0.255042,-0.2550624 -0.619388,-0.2550624 -0.364347,0 -0.619389,0.2550624 -0.255042,0.2550623 -0.255042,0.6194121 0,0.3643497 0.255042,0.6193246 0.255042,0.2550623 0.619389,0.2550623 0.364346,0 0.619388,-0.2550623 0.255042,-0.2549749 0.255042,-0.6193246 z m 0.874431,0 q 0,0.4736372 -0.236825,0.8789369 -0.236824,0.4052998 -0.637606,0.6330621 -0.01822,2.6142358 -2.058555,3.7709858 -0.619388,0.346149 -1.849057,0.737799 -1.165908,0.36435 -1.543916,0.646712 -0.378009,0.282363 -0.378009,0.910875 l 0,0.236862 q 0.40078,0.227675 0.637605,0.633062 0.236825,0.4053 0.236825,0.878937 0,0.7287 -0.510084,1.238824 -0.510085,0.510038 -1.238777,0.510038 -0.728692,0 -1.238777,-0.510038 -0.510085,-0.510124 -0.510085,-1.238824 0,-0.473637 0.236825,-0.878937 0.236826,-0.405387 0.637606,-0.633062 l 0,-7.469083 q -0.40078,-0.2277624 -0.637606,-0.6331496 -0.236825,-0.4052998 -0.236825,-0.878937 0,-0.7286996 0.510085,-1.2388242 0.510085,-0.5100372 1.238777,-0.5100372 0.728692,0 1.238777,0.5100372 0.510084,0.5101246 0.510084,1.2388242 0,0.4736372 -0.236825,0.878937 -0.236825,0.4053872 -0.637605,0.6331496 l 0,4.526985 q 0.491866,-0.236862 1.402732,-0.519225 0.500976,-0.154875 0.797007,-0.268712 0.296031,-0.1138373 0.64216,-0.2823623 0.346129,-0.168525 0.537411,-0.3598 0.191281,-0.191275 0.3689,-0.4645374 0.177619,-0.2732623 0.255042,-0.6330621 0.07742,-0.3597998 0.07742,-0.833437 -0.40078,-0.2277623 -0.637606,-0.6330621 -0.236824,-0.4052997 -0.236824,-0.8789369 0,-0.7286996 0.510084,-1.2388243 0.510085,-0.5101246 1.238777,-0.5101246 0.728693,0 1.238777,0.5101246 0.510084,0.5101247 0.510084,1.2388243 z"/>
|
||||
|
||||
</g>
|
||||
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,Arial,Geneva,sans-serif" font-size="11">
|
||||
<a xlink:href="https://github.com/danielpalme/ReportGenerator" target="_top">
|
||||
<title>Generated by: ReportGenerator 5.1.17.0</title>
|
||||
<use xlink:href="#icon" transform="translate(3,1) scale(3.5)"/>
|
||||
</a>
|
||||
|
||||
<text x="53" y="15" fill="#010101" fill-opacity=".3">Coverage</text>
|
||||
<text x="53" y="14" fill="#fff">Coverage</text>
|
||||
|
||||
<text class="" x="132.5" y="15" fill="#010101" fill-opacity=".3">N/A</text><text class="" x="132.5" y="14">N/A</text>
|
||||
|
||||
</g>
|
||||
|
||||
<g>
|
||||
|
||||
<rect class="" x="90" y="0" width="65" height="20" fill-opacity="0"><title>Branch coverage</title></rect>
|
||||
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
113
badges/line_coverage.svg
Normal file
113
badges/line_coverage.svg
Normal file
@@ -0,0 +1,113 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="155" height="20">
|
||||
<style type="text/css">
|
||||
<![CDATA[
|
||||
@keyframes fade1 {
|
||||
0% { visibility: visible; opacity: 1; }
|
||||
27% { visibility: visible; opacity: 1; }
|
||||
33% { visibility: hidden; opacity: 0; }
|
||||
60% { visibility: hidden; opacity: 0; }
|
||||
66% { visibility: hidden; opacity: 0; }
|
||||
93% { visibility: hidden; opacity: 0; }
|
||||
100% { visibility: visible; opacity: 1; }
|
||||
}
|
||||
@keyframes fade2 {
|
||||
0% { visibility: hidden; opacity: 0; }
|
||||
27% { visibility: hidden; opacity: 0; }
|
||||
33% { visibility: visible; opacity: 1; }
|
||||
60% { visibility: visible; opacity: 1; }
|
||||
66% { visibility: hidden; opacity: 0; }
|
||||
93% { visibility: hidden; opacity: 0; }
|
||||
100% { visibility: hidden; opacity: 0; }
|
||||
}
|
||||
@keyframes fade3 {
|
||||
0% { visibility: hidden; opacity: 0; }
|
||||
27% { visibility: hidden; opacity: 0; }
|
||||
33% { visibility: hidden; opacity: 0; }
|
||||
60% { visibility: hidden; opacity: 0; }
|
||||
66% { visibility: visible; opacity: 1; }
|
||||
93% { visibility: visible; opacity: 1; }
|
||||
100% { visibility: hidden; opacity: 0; }
|
||||
}
|
||||
.linecoverage {
|
||||
animation-duration: 15s;
|
||||
animation-name: fade1;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
.branchcoverage {
|
||||
animation-duration: 15s;
|
||||
animation-name: fade2;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
.methodcoverage {
|
||||
animation-duration: 15s;
|
||||
animation-name: fade3;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
]]>
|
||||
</style>
|
||||
<title>Code coverage</title>
|
||||
<defs>
|
||||
<linearGradient id="gradient" x2="0" y2="100%">
|
||||
<stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
|
||||
<stop offset="1" stop-opacity=".1"/>
|
||||
</linearGradient>
|
||||
|
||||
<linearGradient id="c">
|
||||
<stop offset="0" stop-color="#d40000"/>
|
||||
<stop offset="1" stop-color="#ff2a2a"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="a">
|
||||
<stop offset="0" stop-color="#e0e0de"/>
|
||||
<stop offset="1" stop-color="#fff"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="b">
|
||||
<stop offset="0" stop-color="#37c837"/>
|
||||
<stop offset="1" stop-color="#217821"/>
|
||||
</linearGradient>
|
||||
<linearGradient xlink:href="#a" id="e" x1="106.44" x2="69.96" y1="-11.96" y2="-46.84" gradientTransform="matrix(-.8426 -.00045 -.00045 -.8426 -94.27 -75.82)" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient xlink:href="#b" id="f" x1="56.19" x2="77.97" y1="-23.45" y2="10.62" gradientTransform="matrix(.8426 .00045 .00045 .8426 94.27 75.82)" gradientUnits="userSpaceOnUse"/>
|
||||
<linearGradient xlink:href="#c" id="g" x1="79.98" x2="132.9" y1="10.79" y2="10.79" gradientTransform="matrix(.8426 .00045 .00045 .8426 94.27 75.82)" gradientUnits="userSpaceOnUse"/>
|
||||
|
||||
<mask id="mask">
|
||||
<rect width="155" height="20" rx="3" fill="#fff"/>
|
||||
</mask>
|
||||
|
||||
<g id="icon" transform="matrix(.04486 0 0 .04481 -.48 -.63)">
|
||||
<rect width="52.92" height="52.92" x="-109.72" y="-27.13" fill="url(#e)" transform="rotate(-135)"/>
|
||||
<rect width="52.92" height="52.92" x="70.19" y="-39.18" fill="url(#f)" transform="rotate(45)"/>
|
||||
<rect width="52.92" height="52.92" x="80.05" y="-15.74" fill="url(#g)" transform="rotate(45)"/>
|
||||
</g>
|
||||
</defs>
|
||||
|
||||
<g mask="url(#mask)">
|
||||
<rect x="0" y="0" width="90" height="20" fill="#444"/>
|
||||
<rect x="90" y="0" width="20" height="20" fill="#c00"/>
|
||||
<rect x="110" y="0" width="45" height="20" fill="#00B600"/>
|
||||
<rect x="0" y="0" width="155" height="20" fill="url(#gradient)"/>
|
||||
</g>
|
||||
|
||||
<g>
|
||||
<path class="" stroke="#fff" d="M94 6.5 h12 M94 10.5 h12 M94 14.5 h12"/>
|
||||
|
||||
|
||||
</g>
|
||||
|
||||
<g fill="#fff" text-anchor="middle" font-family="Verdana,Arial,Geneva,sans-serif" font-size="11">
|
||||
<a xlink:href="https://github.com/danielpalme/ReportGenerator" target="_top">
|
||||
<title>Generated by: ReportGenerator 5.1.17.0</title>
|
||||
<use xlink:href="#icon" transform="translate(3,1) scale(3.5)"/>
|
||||
</a>
|
||||
|
||||
<text x="53" y="15" fill="#010101" fill-opacity=".3">Coverage</text>
|
||||
<text x="53" y="14" fill="#fff">Coverage</text>
|
||||
<text class="" x="132.5" y="15" fill="#010101" fill-opacity=".3">100%</text><text class="" x="132.5" y="14">100%</text>
|
||||
|
||||
|
||||
</g>
|
||||
|
||||
<g>
|
||||
<rect class="" x="90" y="0" width="65" height="20" fill-opacity="0"><title>Line coverage</title></rect>
|
||||
|
||||
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
53
coverage.ps1
Normal file
53
coverage.ps1
Normal file
@@ -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 <ProjectReference> 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")
|
||||
79
coverage.sh
Normal file
79
coverage.sh
Normal file
@@ -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 <ProjectReference> 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
|
||||
0
coverage/.gdignore
Normal file
0
coverage/.gdignore
Normal file
103
cspell.json
Normal file
103
cspell.json
Normal file
@@ -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"
|
||||
]
|
||||
}
|
||||
3
default_bus_layout.tres
Normal file
3
default_bus_layout.tres
Normal file
@@ -0,0 +1,3 @@
|
||||
[gd_resource type="AudioBusLayout" format=3 uid="uid://bx2uo1td15816"]
|
||||
|
||||
[resource]
|
||||
0
docs/.gdignore
Normal file
0
docs/.gdignore
Normal file
BIN
docs/publish.png
Normal file
BIN
docs/publish.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
BIN
docs/renovatebot_pr.png
Normal file
BIN
docs/renovatebot_pr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 89 KiB |
BIN
docs/spelling_fix.png
Normal file
BIN
docs/spelling_fix.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
BIN
docs/version_change.png
Normal file
BIN
docs/version_change.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 134 KiB |
9
global.json
Normal file
9
global.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "10.0.104",
|
||||
"rollForward": "major"
|
||||
},
|
||||
"msbuild-sdks": {
|
||||
"Godot.NET.Sdk": "4.6.2"
|
||||
}
|
||||
}
|
||||
40
icon.png.import
Normal file
40
icon.png.import
Normal file
@@ -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
|
||||
6
nuget.config
Normal file
6
nuget.config
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<configuration>
|
||||
<packageSources>
|
||||
<add key="coverlet" value="https://pkgs.dev.azure.com/tonerdo/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json" />
|
||||
</packageSources>
|
||||
</configuration>
|
||||
20
omnisharp.json
Normal file
20
omnisharp.json
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
54
project.godot
Normal file
54
project.godot
Normal file
@@ -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"
|
||||
4
renovate.json
Normal file
4
renovate.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["github>chickensoft-games/renovate:godot"]
|
||||
}
|
||||
71
src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs
Normal file
71
src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.cs
Normal file
@@ -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;});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://dgyuh6un1qoj4
|
||||
34
src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.tscn
Normal file
34
src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.tscn
Normal file
@@ -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")
|
||||
133
src/ESCThing/StructuralInstance.Builder.cs
Normal file
133
src/ESCThing/StructuralInstance.Builder.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
// public partial class StructuralInstance<TAttribute> where TAttribute : class
|
||||
// {
|
||||
public sealed class StructuralBuilder<TAttribute> where TAttribute : class
|
||||
{
|
||||
|
||||
private readonly Dictionary<int, TAttribute> _components = [];
|
||||
|
||||
public StructuralBuilder() { }
|
||||
|
||||
public StructuralBuilder(IStructuralInstance<TAttribute> 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<TAttribute> Add<T>(T component) where T : TAttribute
|
||||
{
|
||||
int id = ComponentTypeRegistry.GetId<T>();
|
||||
_components[id] = component;
|
||||
return this;
|
||||
}
|
||||
public StructuralBuilder<TAttribute> Upsert<T>(Func<T, T> ifExists, Func<T> none) where T : TAttribute
|
||||
{
|
||||
int id = ComponentTypeRegistry.GetId<T>();
|
||||
_components[id] = _components.TryGetValue(id, out var old) ? ifExists((T)old) : none();
|
||||
return this;
|
||||
}
|
||||
public StructuralBuilder<TAttribute> CombineWith(IStructuralInstance<TAttribute> other, Action<CombineBinder>? 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<TAttribute> Remove<T>() where T : TAttribute
|
||||
{
|
||||
int id = ComponentTypeRegistry.GetId<T>();
|
||||
_components.Remove(id);
|
||||
return this;
|
||||
}
|
||||
|
||||
public bool Has<T>() where T : TAttribute
|
||||
{
|
||||
int id = ComponentTypeRegistry.GetId<T>();
|
||||
return _components.ContainsKey(id);
|
||||
}
|
||||
|
||||
public StructuralInstance<TAttribute> 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<TAttribute>(typeIds, components);
|
||||
}
|
||||
public MutableStructuralInstance<TAttribute> BuildMutable()
|
||||
{
|
||||
// int count = _components.Count;
|
||||
|
||||
var sortedList = new SortedList<int, TAttribute>(_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<TAttribute>(sortedList);
|
||||
}
|
||||
public class CombineBinder
|
||||
{
|
||||
internal readonly Dictionary<int, Func<TAttribute, TAttribute, TAttribute>> _rules = [];
|
||||
internal readonly HashSet<int> _ignores = [];
|
||||
public CombineBinder Bind<T>(Func<T, T, T> func) where T : TAttribute
|
||||
{
|
||||
_rules[ComponentType<T>.Id] = (a, b) => func((T)a, (T)b);
|
||||
return this;
|
||||
}
|
||||
public CombineBinder Ignore<T>() where T : TAttribute
|
||||
{
|
||||
_ignores.Add(ComponentType<T>.Id);
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// }
|
||||
1
src/ESCThing/StructuralInstance.Builder.cs.uid
Normal file
1
src/ESCThing/StructuralInstance.Builder.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bk721rnhegl0x
|
||||
255
src/ESCThing/StructuralInstance.cs
Normal file
255
src/ESCThing/StructuralInstance.cs
Normal file
@@ -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<TAttribute> : IEquatable<IStructuralInstance<TAttribute>>
|
||||
{
|
||||
int ComponentCount { get; }
|
||||
bool Has<T>() where T : TAttribute;
|
||||
Option<T> Get<T>() where T : TAttribute;
|
||||
// TAttribute GetComponentAt(int index);
|
||||
IEnumerable<(int Id, TAttribute Value)> GetAttributes();
|
||||
bool IEquatable<IStructuralInstance<TAttribute>>.Equals(IStructuralInstance<TAttribute>? 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<TAttribute> : IStructuralInstance<TAttribute>, IEquatable<StructuralInstance<TAttribute>> 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<TAttribute>).ComputeHashCode();
|
||||
}
|
||||
|
||||
public bool Has<T>() where T : TAttribute
|
||||
{
|
||||
// int typeId = ComponentTypeRegistry.GetId<T>();
|
||||
var typeId = ComponentType<T>.Id;
|
||||
return Array.BinarySearch(_attributesTypeIds, typeId) >= 0;
|
||||
}
|
||||
|
||||
public Option<T> Get<T>() where T : TAttribute
|
||||
{
|
||||
// int typeId = ComponentTypeRegistry.GetId<T>();
|
||||
var typeId = ComponentType<T>.Id;
|
||||
int index = Array.BinarySearch(_attributesTypeIds, typeId);
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
return Option<T>.None;
|
||||
}
|
||||
|
||||
return Option<T>.Some((T)_attributes[index]);
|
||||
}
|
||||
|
||||
public bool Equals(StructuralInstance<TAttribute>? other)
|
||||
{
|
||||
if (ReferenceEquals(this, other))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (other is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return _hashCode == other._hashCode && Enumerable.SequenceEqual(_attributes, other._attributes, EqualityComparer<TAttribute>.Default);// && AttributesAreEqual(other);
|
||||
}
|
||||
public override bool Equals(object? obj) => obj is StructuralInstance<TAttribute> value? Equals(value) : obj is IStructuralInstance<TAttribute> 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<TAttribute> : IStructuralInstance<TAttribute>, IEquatable<MutableStructuralInstance<TAttribute>> where TAttribute : class
|
||||
{
|
||||
private readonly SortedList<int, TAttribute> _attributes = [];
|
||||
public int ComponentCount => _attributes.Count;
|
||||
public MutableStructuralInstance(SortedList<int, TAttribute> attributes)
|
||||
{
|
||||
_attributes = attributes;
|
||||
}
|
||||
public Option<T> Get<T>() where T : TAttribute => _attributes.TryGetValue(ComponentType<T>.Id, out var attribute) ? Option<T>.Some((T)attribute) : Option<T>.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<T>() where T : TAttribute => _attributes.ContainsKey(ComponentType<T>.Id);
|
||||
public void Set<T>(T value) where T : TAttribute => _attributes[ComponentType<T>.Id] = value;
|
||||
public override int GetHashCode() => (this as IStructuralInstance<TAttribute>).ComputeHashCode();
|
||||
public bool Equals(MutableStructuralInstance<TAttribute>? other) => other is not null && Enumerable.SequenceEqual(_attributes, other._attributes, EqualityComparer<KeyValuePair<int, TAttribute>>.Default);
|
||||
public override bool Equals(object? obj) => obj is MutableStructuralInstance<TAttribute> other ? Equals(other) : obj is IStructuralInstance<TAttribute> v && v.Equals(this);
|
||||
}
|
||||
|
||||
public static class ComponentTypeRegistry
|
||||
{
|
||||
private static readonly Dictionary<Type, int> _typeToId = [];
|
||||
private static readonly List<Type> _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<T>() => GetId(typeof(T));
|
||||
}
|
||||
public static class ComponentType<T>
|
||||
{
|
||||
public static readonly int Id = ComponentTypeRegistry.GetId<T>();
|
||||
public static readonly bool CacheTransitions = Resolve();
|
||||
|
||||
private static bool Resolve()
|
||||
{
|
||||
var attr = typeof(T).GetCustomAttributes(typeof(ComponentOptionsAttribute), false);
|
||||
return attr.OfType<ComponentOptionsAttribute>().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<TAttribute> where TAttribute : class
|
||||
{
|
||||
StructuralInstance<TAttribute> Canonicalize(StructuralInstance<TAttribute> value);
|
||||
StructuralInstance<TAttribute> AddOrReplaceAttribute<T>(StructuralInstance<TAttribute> instance, T attribute) where T : TAttribute;
|
||||
StructuralInstance<TAttribute> RemoveAttribute<T>(StructuralInstance<TAttribute> instance) where T : TAttribute;
|
||||
}
|
||||
public class StructuralInstanceManger<TAttribute> : IStructureRegistry<TAttribute> where TAttribute : class
|
||||
{
|
||||
private readonly HashSet<StructuralInstance<TAttribute>> _instances = [];
|
||||
public StructuralInstance<TAttribute> Add<T>(StructuralInstance<TAttribute> instance, T component) where T : TAttribute
|
||||
{
|
||||
var builder = new StructuralBuilder<TAttribute>(instance).Add(component);
|
||||
return Canonicalize(builder.Build());
|
||||
|
||||
}
|
||||
public StructuralInstance<TAttribute> Canonicalize(StructuralInstance<TAttribute> value)
|
||||
{
|
||||
|
||||
if (_instances.TryGetValue(value, out var id))
|
||||
{
|
||||
return id;
|
||||
}
|
||||
_instances.Add(value);
|
||||
return value;
|
||||
}
|
||||
public StructuralInstance<TAttribute> AddOrReplaceAttribute<T>(StructuralInstance<TAttribute> 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<T> addTransitionEntry && EqualityComparer<T>.Default.Equals(addTransitionEntry.Value, attribute))
|
||||
{
|
||||
return addTransitionEntry.Result;
|
||||
}
|
||||
}
|
||||
var result =new AddTransitionEntry<T>(ComponentType<T>.Id, attribute, Canonicalize(new StructuralBuilder<TAttribute>(instance).Add(attribute).Build()));
|
||||
if (ComponentType<T>.CacheTransitions)
|
||||
{
|
||||
results.Add(result);
|
||||
}
|
||||
return result.Result;
|
||||
}
|
||||
public StructuralInstance<TAttribute> RemoveAttribute<T>(StructuralInstance<TAttribute> instance) where T : TAttribute
|
||||
{
|
||||
if (!graph.TryGetValue(instance, out var results))
|
||||
{
|
||||
graph[instance] = results = [];
|
||||
}
|
||||
var id = ComponentType<T>.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<TAttribute>(instance).Remove<T>().Build()));
|
||||
if (ComponentType<T>.CacheTransitions)
|
||||
{
|
||||
results.Add(result);
|
||||
}
|
||||
return result.Result;
|
||||
}
|
||||
private Dictionary<StructuralInstance<TAttribute>, List<TransitionEntry>> graph = [];
|
||||
|
||||
private record TransitionEntry(int Id);
|
||||
|
||||
private record AddTransitionEntry<T>(int Id, T Value, StructuralInstance<TAttribute> Result) : TransitionEntry(Id) where T : TAttribute;
|
||||
|
||||
private record RemoveTransitionEntry(int Id, StructuralInstance<TAttribute> Result) : TransitionEntry(Id);
|
||||
// private record ModifyTransitionEntry<T>(int Id, TAttribute Value, StructuralInstance<TAttribute> Result) : TransitionEntry(Id) where T : TAttribute;
|
||||
|
||||
}
|
||||
public class AutoStructureInstance<TAttribute> where TAttribute : class
|
||||
{
|
||||
private StructuralInstance<TAttribute> _structuralInstance;
|
||||
private IStructureRegistry<TAttribute> _registry;
|
||||
private Dictionary<int,List<Delegate>> _bindings = [];
|
||||
|
||||
public void Set<T>(T? value) where T : TAttribute
|
||||
{
|
||||
var old = _structuralInstance.Get<T>();
|
||||
if (old.HasValue && old.Value.Equals(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (value is null)
|
||||
{
|
||||
_structuralInstance = _registry.RemoveAttribute<T>(_structuralInstance);
|
||||
}
|
||||
else
|
||||
{
|
||||
_structuralInstance = _registry.AddOrReplaceAttribute(_structuralInstance, value);
|
||||
}
|
||||
_bindings[ComponentType<T>.Id].ForEach(item => item.DynamicInvoke(old,value));
|
||||
|
||||
}
|
||||
public void Bind<T>(Action<T?, T?> callback) where T : TAttribute
|
||||
{
|
||||
int id = ComponentType<T>.Id;
|
||||
|
||||
if (!_bindings.TryGetValue(id, out var list))
|
||||
{
|
||||
_bindings[id] = list = [];
|
||||
}
|
||||
|
||||
list.Add(callback);
|
||||
}
|
||||
}
|
||||
1
src/ESCThing/StructuralInstance.cs.uid
Normal file
1
src/ESCThing/StructuralInstance.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cexmjk01dgtbe
|
||||
69
src/ESCThing/TestStructural.cs
Normal file
69
src/ESCThing/TestStructural.cs
Normal file
@@ -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<object>()
|
||||
.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<object>();
|
||||
var b = registy.Add(a,new Vector2(1,2));
|
||||
var c = registy.Add(a,new Vector2(1,2));
|
||||
var d = registy.Canonicalize(new StructuralBuilder<object>(a).Add(new Vector2(1,2)).Build());
|
||||
var g = new StructuralBuilder<object>(a).Add(Option<int>.Some(5)).Build();
|
||||
var h = new StructuralBuilder<object>(a).Add(Option<int>.Some(5)).BuildMutable();
|
||||
GD.Print(g.Equals(h));
|
||||
GD.Print(ReferenceEquals(b,c));
|
||||
GD.Print(ReferenceEquals(b,d));
|
||||
GD.Print(ComponentType<Vector3>.Id);
|
||||
GD.Print(ComponentType<Vector2>.Id);
|
||||
|
||||
var help = new StructuralBuilder<object>(a)
|
||||
.CombineWith(b, static configure => configure
|
||||
.Bind<Vector2>(static (a, b) => a + b)
|
||||
.Ignore<Vector4>())
|
||||
.Add(Vector2I.Zero)
|
||||
.Build();
|
||||
GD.Print(help.Get<Vector2>().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<object>(a).Add(new test()).Build());
|
||||
}
|
||||
timer.Stop();
|
||||
GD.Print(timer.Elapsed);
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
[ComponentOptions(CacheTransitions = true)]
|
||||
record test
|
||||
{
|
||||
public int a;
|
||||
public string b;
|
||||
}
|
||||
1
src/ESCThing/TestStructural.cs.uid
Normal file
1
src/ESCThing/TestStructural.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://br6rfqtryq0cx
|
||||
15
src/Game.cs
Normal file
15
src/Game.cs
Normal file
@@ -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<Button>("%TestButton");
|
||||
|
||||
public void OnTestButtonPressed() => ButtonPresses++;
|
||||
|
||||
}
|
||||
1
src/Game.cs.uid
Normal file
1
src/Game.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bcadf3uhcfy2
|
||||
30
src/Game.tscn
Normal file
30
src/Game.tscn
Normal file
@@ -0,0 +1,30 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://cywpu6lxdjhuu"]
|
||||
|
||||
[ext_resource type="Script" path="res://src/Game.cs" id="1_17mmo"]
|
||||
|
||||
[node name="Control" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_17mmo")
|
||||
|
||||
[node name="CenterContainer" type="CenterContainer" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="CenterContainer"]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="TestButton" type="Button" parent="CenterContainer/VBoxContainer"]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
text = "Test Button"
|
||||
|
||||
[connection signal="pressed" from="CenterContainer/VBoxContainer/TestButton" to="." method="OnTestButtonPressed"]
|
||||
268
src/Items/Item.cs
Normal file
268
src/Items/Item.cs
Normal file
@@ -0,0 +1,268 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Godot;
|
||||
|
||||
/// <summary>
|
||||
/// Items that can be on a belt
|
||||
/// </summary>
|
||||
public interface IBeltItem : IDisposable
|
||||
{
|
||||
int Width { get; }
|
||||
int Height { get; }
|
||||
Node3D CreateItemVisual();
|
||||
delegate void ItemRemoved(IBeltItem item);
|
||||
event ItemRemoved Disposed;
|
||||
}
|
||||
|
||||
public class TestItem() : IBeltItem
|
||||
{
|
||||
public float Temp {get;set;}
|
||||
public int Width {get;set;}
|
||||
|
||||
public int Height {get;set;}
|
||||
|
||||
public Node3D CreateItemVisual() => new MeshInstance3D(){Mesh = new BoxMesh(){Size = new(.1f,.1f,.1f)}};
|
||||
bool _disposed;
|
||||
|
||||
public event IBeltItem.ItemRemoved Disposed;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
Disposed?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IFluidItem
|
||||
{
|
||||
float Volume { get; }
|
||||
}
|
||||
public interface IConveyorProfile
|
||||
{
|
||||
int LaneCount { get; }
|
||||
bool LaneSwitching { get; }
|
||||
|
||||
}
|
||||
public readonly record struct BeltPortProfile(
|
||||
GridTransform3D LocalOffset,
|
||||
// Direction Face,
|
||||
int Width,
|
||||
PortAccess Access
|
||||
) : IBeltSlotProfile
|
||||
{
|
||||
public Vector3I Position => LocalOffset.Origin;
|
||||
// Direction IBeltSlotProfile.Direction => Face;
|
||||
}
|
||||
public interface IBeltPort
|
||||
{
|
||||
BeltPortProfile Profile { get; }
|
||||
|
||||
bool CanAccept(
|
||||
IBeltItem item,
|
||||
LaneSpan laneSpan,
|
||||
float beltT
|
||||
);
|
||||
|
||||
bool TryInsert(
|
||||
IBeltItem item,
|
||||
LaneSpan laneSpan,
|
||||
float beltT
|
||||
);
|
||||
IEnumerable<Vector3I> Points()
|
||||
{
|
||||
for (int i = 0; i < Profile.Width; i++)
|
||||
{
|
||||
yield return (Profile.LocalOffset.Right * i) + Profile.LocalOffset.Origin;
|
||||
}
|
||||
}
|
||||
LaneSpan LaneSpan => new LaneSpan(0,(ushort)Profile.Width);
|
||||
}
|
||||
public sealed class ConveyorPort : IBeltPort
|
||||
{
|
||||
public BeltPortProfile Profile { get; }
|
||||
public IMovementConveyor Conveyor { get; }
|
||||
public ItemConveyor.BeltT BeltT { get; }
|
||||
private readonly Func<IBeltItem, float, LaneSpan, bool>? _canAccept;
|
||||
private readonly Func<IBeltItem, float, LaneSpan, bool>? _accept;
|
||||
|
||||
public ConveyorPort(
|
||||
IMovementConveyor conveyor,
|
||||
BeltPortProfile profile,
|
||||
ItemConveyor.BeltT beltT,
|
||||
Func<IBeltItem, float, LaneSpan, bool>? canAccept,
|
||||
Func<IBeltItem, float, LaneSpan, bool>? accept)
|
||||
{
|
||||
Conveyor = conveyor;
|
||||
Profile = profile;
|
||||
BeltT = beltT;
|
||||
_canAccept = canAccept;
|
||||
_accept = accept;
|
||||
}
|
||||
|
||||
public bool CanAccept(
|
||||
IBeltItem item,
|
||||
LaneSpan laneSpan,
|
||||
float beltT)
|
||||
{
|
||||
if (!Profile.Access.HasFlag(PortAccess.In))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_canAccept is null || _accept is null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// LaneSpan checks live here, not in delegates
|
||||
if (!LaneSpan.Encapsulates(
|
||||
new LaneSpan(0, (ushort)Profile.Width),
|
||||
laneSpan))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return _canAccept(item, beltT, laneSpan);
|
||||
}
|
||||
|
||||
public bool TryInsert(
|
||||
IBeltItem item,
|
||||
LaneSpan laneSpan,
|
||||
float beltT)
|
||||
{
|
||||
if (!CanAccept(item, laneSpan, beltT))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _accept!(item, beltT, laneSpan);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public interface IBeltSlotProfile
|
||||
{
|
||||
Vector3I Position { get; }
|
||||
// Direction Direction { get; }
|
||||
int Width { get; }
|
||||
PortAccess Access { get; }
|
||||
// bool CanAcceptItem(IBeltItem beltItem, LaneSpan laneSpan, float beltT = 0);
|
||||
// /// <summary>
|
||||
// ///Tries to insert an belt item offset by laneSpan,
|
||||
// /// </summary>
|
||||
// /// <param name="beltItem"></param>
|
||||
// /// <param name="laneSpan"></param>
|
||||
// /// <param name="beltT"></param>
|
||||
// /// <returns></returns>
|
||||
// bool TryInsertItem(IBeltItem beltItem, LaneSpan laneSpan, float beltT = 0);
|
||||
}
|
||||
public record LaneId(int Index);
|
||||
[Flags]
|
||||
public enum PortAccess : byte
|
||||
{
|
||||
// Disabled = 0,
|
||||
In = 1,
|
||||
Out = 2,
|
||||
InOut = In | Out,
|
||||
BiDirectional = InOut
|
||||
}
|
||||
[Flags]
|
||||
public enum TransferMode : byte//Need Better Name
|
||||
{
|
||||
Passive = 0,
|
||||
Push = 1,
|
||||
Pull = 2,
|
||||
PushPull = Push | Pull
|
||||
}
|
||||
public static class SlotExtesion
|
||||
{
|
||||
public static LaneSpan MapLaneSpanToFacingPort(this IBeltPort self, IBeltPort other) => MapSlotToFacingSlot(self.Profile.LocalOffset, self.Profile.Width, other.Profile.LocalOffset, other.Profile.Width);
|
||||
|
||||
public static LaneSpan MapSlotToFacingSlot(
|
||||
GridTransform3D fromTx,
|
||||
int fromWidth,
|
||||
GridTransform3D toTx,
|
||||
int toWidth)
|
||||
{
|
||||
ushort minLane = ushort.MaxValue;
|
||||
ushort maxLane = ushort.MinValue;
|
||||
|
||||
// In port-local space:
|
||||
// +Z = forward
|
||||
// +X = width axis
|
||||
// origin = port center / lane 0 start
|
||||
// (adjust if your convention differs)
|
||||
for (int i = 0; i < fromWidth; i++)
|
||||
{
|
||||
var fromLocalLane = new Vector3I(i, 0, 0);
|
||||
|
||||
var fromLocalFacing = fromLocalLane + Vector3I.Forward;
|
||||
|
||||
var worldFacing = fromTx.LocalToWorld(fromLocalFacing);
|
||||
|
||||
var toLocal = toTx.WorldToLocal(worldFacing);
|
||||
|
||||
int laneIndex = toLocal.X;
|
||||
|
||||
bool inWidth = laneIndex >= 0 && laneIndex < toWidth;
|
||||
bool onFacePlane = toLocal.Z == 0; // directly entering target face
|
||||
|
||||
if (!inWidth || !onFacePlane)
|
||||
continue;
|
||||
|
||||
minLane = (ushort)Math.Min(minLane, laneIndex);
|
||||
maxLane = (ushort)Math.Max(maxLane, laneIndex);
|
||||
}
|
||||
|
||||
if (minLane > maxLane)
|
||||
return LaneSpan.Zero;
|
||||
return new LaneSpan(minLane, (ushort)(maxLane + 1));
|
||||
}
|
||||
|
||||
|
||||
// Map a sub-span from 'from' port into the 'to' port space
|
||||
public static LaneSpan MapLaneSpan(this IBeltPort from, IBeltPort to, LaneSpan incoming)
|
||||
{
|
||||
ushort minLane = ushort.MaxValue;
|
||||
ushort maxLane = ushort.MinValue;
|
||||
|
||||
// Loop over each lane in the incoming span
|
||||
for (int i = incoming.Start; i < incoming.End; i++)
|
||||
{
|
||||
// from-local lane
|
||||
var fromLocalLane = new Vector3I(i, 0, 0);
|
||||
|
||||
// shift forward along +Z to "face" the target
|
||||
var fromLocalFacing = fromLocalLane + Vector3I.Forward;
|
||||
|
||||
// world space
|
||||
var worldFacing = from.Profile.LocalOffset.LocalToWorld(fromLocalFacing);
|
||||
|
||||
// target local space
|
||||
var toLocal = to.Profile.LocalOffset.WorldToLocal(worldFacing);
|
||||
|
||||
int laneIndex = toLocal.X;
|
||||
|
||||
bool inWidth = laneIndex >= 0 && laneIndex < to.Profile.Width;
|
||||
bool onFacePlane = toLocal.Z == 0; // entering target face
|
||||
|
||||
if (!inWidth || !onFacePlane)
|
||||
continue;
|
||||
|
||||
minLane = (ushort)Math.Min(minLane, laneIndex);
|
||||
maxLane = (ushort)Math.Max(maxLane, laneIndex);
|
||||
}
|
||||
|
||||
if (minLane > maxLane)
|
||||
return LaneSpan.Zero;
|
||||
|
||||
return new LaneSpan(minLane, (ushort)(maxLane + 1));
|
||||
}
|
||||
}
|
||||
1
src/Items/Item.cs.uid
Normal file
1
src/Items/Item.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dacksxt66lea4
|
||||
52
src/Main.cs
Normal file
52
src/Main.cs
Normal file
@@ -0,0 +1,52 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using Godot;
|
||||
using Chickensoft.GameTools.Displays;
|
||||
|
||||
#if RUN_TESTS
|
||||
using System.Reflection;
|
||||
using Chickensoft.GoDotTest;
|
||||
#endif
|
||||
|
||||
// This entry-point file is responsible for determining if we should run tests.
|
||||
//
|
||||
// If you want to edit your game's main entry-point, please see Game.tscn and
|
||||
// Game.cs instead.
|
||||
|
||||
public partial class Main : Node2D
|
||||
{
|
||||
public Vector2I DesignResolution => Display.UHD4k;
|
||||
#if RUN_TESTS
|
||||
public TestEnvironment Environment = default!;
|
||||
#endif
|
||||
|
||||
public override void _Ready()
|
||||
{
|
||||
// Correct any erroneous scaling and guess sensible defaults.
|
||||
GetWindow().LookGood(WindowScaleBehavior.UIFixed, DesignResolution);
|
||||
|
||||
#if RUN_TESTS
|
||||
// If this is a debug build, use GoDotTest to examine the
|
||||
// command line arguments and determine if we should run tests.
|
||||
Environment = TestEnvironment.From(OS.GetCmdlineArgs());
|
||||
// Environment = new TestEnvironment(true, false, true, false, false, false, null, OS.GetCmdlineArgs());
|
||||
GD.Print(Environment.ShouldRunTests);
|
||||
if (Environment.ShouldRunTests)
|
||||
{
|
||||
CallDeferred("RunTests");
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
// If we don't need to run tests, we can just switch to the game scene.
|
||||
CallDeferred("RunScene");
|
||||
}
|
||||
|
||||
#if RUN_TESTS
|
||||
private void RunTests()
|
||||
=> _ = GoTest.RunTests(Assembly.GetExecutingAssembly(), this, Environment);
|
||||
#endif
|
||||
|
||||
private void RunScene()
|
||||
=> GetTree().ChangeSceneToFile("res://src/Game.tscn");
|
||||
}
|
||||
1
src/Main.cs.uid
Normal file
1
src/Main.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://d0cxxx3axr1j
|
||||
6
src/Main.tscn
Normal file
6
src/Main.tscn
Normal file
@@ -0,0 +1,6 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://du7hlwjl6vw6l"]
|
||||
|
||||
[ext_resource type="Script" path="res://src/Main.cs" id="1_prpoe"]
|
||||
|
||||
[node name="Node2D" type="Node2D"]
|
||||
script = ExtResource("1_prpoe")
|
||||
222
src/Math/GridTransform3D.cs
Normal file
222
src/Math/GridTransform3D.cs
Normal file
@@ -0,0 +1,222 @@
|
||||
using Godot;
|
||||
using System;
|
||||
|
||||
public readonly struct GridTransform3D : IEquatable<GridTransform3D>
|
||||
{
|
||||
public readonly Vector3I Origin;
|
||||
|
||||
// Basis vectors (columns of a rotation matrix)
|
||||
public readonly Vector3I Right; // local +X
|
||||
public readonly Vector3I Up; // local +Y
|
||||
public readonly Vector3I Forward; // local +Z
|
||||
|
||||
#region Constructors
|
||||
|
||||
public GridTransform3D(
|
||||
Vector3I origin,
|
||||
Vector3I right,
|
||||
Vector3I up,
|
||||
Vector3I forward)
|
||||
{
|
||||
Origin = origin;
|
||||
Right = right;
|
||||
Up = up;
|
||||
Forward = forward;
|
||||
}
|
||||
|
||||
public static GridTransform3D Identity =>
|
||||
new(
|
||||
Vector3I.Zero,
|
||||
Vector3I.Right,
|
||||
Vector3I.Up,
|
||||
Vector3I.Forward
|
||||
);
|
||||
|
||||
public static GridTransform3D At(Vector3I origin) =>
|
||||
new(origin, Vector3I.Right, Vector3I.Up, Vector3I.Forward);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Transform ops
|
||||
|
||||
/// Transform a local point into world space
|
||||
public Vector3I TransformPoint(Vector3I local)
|
||||
{
|
||||
return Origin
|
||||
+ (Right * local.X)
|
||||
+ (Up * local.Y)
|
||||
+ (Forward * local.Z);
|
||||
}
|
||||
|
||||
/// Transform a direction (ignores translation)
|
||||
public Vector3I TransformDirection(Vector3I dir)
|
||||
{
|
||||
return (Right * dir.X)
|
||||
+ (Up * dir.Y)
|
||||
+ (Forward * dir.Z);
|
||||
}
|
||||
public Vector3I LocalToWorld(Vector3I local)
|
||||
{
|
||||
return Origin
|
||||
+ Right * local.X
|
||||
+ Up * local.Y
|
||||
+ Forward * local.Z;
|
||||
}
|
||||
public Vector3I WorldToLocal(Vector3I world)
|
||||
{
|
||||
Vector3I delta = world - Origin;
|
||||
|
||||
return new Vector3I(
|
||||
Dot(delta, Right),
|
||||
Dot(delta, Up),
|
||||
Dot(delta, Forward)
|
||||
);
|
||||
}
|
||||
private static int Dot(Vector3I a, Vector3I b)
|
||||
=> a.X * b.X + a.Y * b.Y + a.Z * b.Z;
|
||||
|
||||
public readonly GridTransform3D Translated(Vector3I offset)
|
||||
{
|
||||
return new GridTransform3D( Origin + offset,Right,Up,Forward);
|
||||
}
|
||||
|
||||
public readonly GridTransform3D TranslatedLocal(Vector3I offset)
|
||||
{
|
||||
return new GridTransform3D( new Vector3I(Origin.X + Dot(Right,offset), Origin.Y+ Dot(Up,offset), Origin.Z + Dot(Forward,offset)),Right,Up,Forward);
|
||||
}
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Composition
|
||||
|
||||
/// Compose two transforms (this * other)
|
||||
public GridTransform3D Multiply(GridTransform3D other)
|
||||
{
|
||||
return new GridTransform3D(
|
||||
TransformPoint(other.Origin),
|
||||
TransformDirection(other.Right),
|
||||
TransformDirection(other.Up),
|
||||
TransformDirection(other.Forward)
|
||||
);
|
||||
}
|
||||
|
||||
public static GridTransform3D operator *(GridTransform3D a, GridTransform3D b)
|
||||
=> a.Multiply(b);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Rotations (90° steps)
|
||||
|
||||
public GridTransform3D RotateY90CW()
|
||||
{
|
||||
// Y stays, X -> Z, Z -> -X
|
||||
return new GridTransform3D(
|
||||
Origin,
|
||||
Forward,
|
||||
Up,
|
||||
-Right
|
||||
);
|
||||
}
|
||||
|
||||
public GridTransform3D RotateY90CCW()
|
||||
{
|
||||
// Y stays, X -> -Z, Z -> X
|
||||
return new GridTransform3D(
|
||||
Origin,
|
||||
-Forward,
|
||||
Up,
|
||||
Right
|
||||
);
|
||||
}
|
||||
|
||||
public GridTransform3D RotateX90CW()
|
||||
{
|
||||
// X stays, Y -> Z, Z -> -Y
|
||||
return new GridTransform3D(
|
||||
Origin,
|
||||
Right,
|
||||
Forward,
|
||||
-Up
|
||||
);
|
||||
}
|
||||
|
||||
public GridTransform3D RotateZ90CW()
|
||||
{
|
||||
// Z stays, X -> Y, Y -> -X
|
||||
return new GridTransform3D(
|
||||
Origin,
|
||||
Up,
|
||||
-Right,
|
||||
Forward
|
||||
);
|
||||
}
|
||||
|
||||
public GridTransform3D RotateY(int quarterTurns)
|
||||
{
|
||||
quarterTurns = ((quarterTurns % 4) + 4) % 4;
|
||||
var t = this;
|
||||
for (int i = 0; i < quarterTurns; i++)
|
||||
t = t.RotateY90CW();
|
||||
return t;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Conversion to/from Godot
|
||||
|
||||
public Transform3D ToGodot()
|
||||
{
|
||||
var basis = new Basis(
|
||||
(Vector3)Right,
|
||||
(Vector3)Up,
|
||||
(Vector3)Forward
|
||||
);
|
||||
|
||||
return new Transform3D(basis, (Vector3)Origin);
|
||||
}
|
||||
|
||||
public static GridTransform3D FromGodot(Transform3D t)
|
||||
{
|
||||
Vector3I RoundAxis(Vector3 v)
|
||||
{
|
||||
var a = v.Abs();
|
||||
if (a.X > a.Y && a.X > a.Z)
|
||||
return new Vector3I(Math.Sign(v.X), 0, 0);
|
||||
if (a.Y > a.Z)
|
||||
return new Vector3I(0, Math.Sign(v.Y), 0);
|
||||
return new Vector3I(0, 0, Math.Sign(v.Z));
|
||||
}
|
||||
|
||||
return new GridTransform3D(
|
||||
new Vector3I(
|
||||
Mathf.RoundToInt(t.Origin.X),
|
||||
Mathf.RoundToInt(t.Origin.Y),
|
||||
Mathf.RoundToInt(t.Origin.Z)
|
||||
),
|
||||
RoundAxis(t.Basis.X),
|
||||
RoundAxis(t.Basis.Y),
|
||||
RoundAxis(t.Basis.Z)
|
||||
);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Equality
|
||||
|
||||
public bool Equals(GridTransform3D other)
|
||||
{
|
||||
return Origin == other.Origin
|
||||
&& Right == other.Right
|
||||
&& Up == other.Up
|
||||
&& Forward == other.Forward;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is GridTransform3D g && Equals(g);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine(Origin, Right, Up, Forward);
|
||||
|
||||
#endregion
|
||||
}
|
||||
1
src/Math/GridTransform3D.cs.uid
Normal file
1
src/Math/GridTransform3D.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dpskwnyblxtf4
|
||||
23
src/SjkScripts/SJKGodotHelpers/Math/LerpVectors.cs
Normal file
23
src/SjkScripts/SJKGodotHelpers/Math/LerpVectors.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using Godot;
|
||||
|
||||
public static partial class SJKMath
|
||||
{
|
||||
// public static Vector3I Lerp(Vector3I from, Vector3I to, float weight) => new Vector3I(
|
||||
// Mathf.Lerp(from.X, to.X, weight),
|
||||
// Mathf.Lerp(from.Y, to.Y, weight),
|
||||
// Mathf.Lerp(from.Z, to.Z, weight)
|
||||
// );
|
||||
public static Vector3 Lerp(Vector3 from, Vector3 to, float weight) => new Vector3(
|
||||
Mathf.Lerp(from.X, to.X, weight),
|
||||
Mathf.Lerp(from.Y, to.Y, weight),
|
||||
Mathf.Lerp(from.Z, to.Z, weight)
|
||||
);
|
||||
// public static Vector2I Lerp(Vector2I from, Vector2I to, float weight) => new Vector2I(
|
||||
// Mathf.Lerp(from.X, to.X, weight),
|
||||
// Mathf.Lerp(from.Y, to.Y, weight)
|
||||
// );
|
||||
public static Vector2 Lerp(Vector2 from, Vector2 to, float weight) => new Vector2(
|
||||
Mathf.Lerp(from.X, to.X, weight),
|
||||
Mathf.Lerp(from.Y, to.Y, weight)
|
||||
);
|
||||
}
|
||||
1
src/SjkScripts/SJKGodotHelpers/Math/LerpVectors.cs.uid
Normal file
1
src/SjkScripts/SJKGodotHelpers/Math/LerpVectors.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://133eop5e4mii
|
||||
42
src/SjkScripts/SJKGodotHelpers/MyNodeExtensions.cs
Normal file
42
src/SjkScripts/SJKGodotHelpers/MyNodeExtensions.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Godot;
|
||||
|
||||
namespace SJK.GodotHelpers;
|
||||
public static class MyNodeExtensions{
|
||||
public static void FreeDeferred(this Node node)=>node.CallDeferred(Node.MethodName.Free);
|
||||
/// <summary>
|
||||
/// Checks if given Property exists on <c>Base</c>
|
||||
/// </summary>
|
||||
/// <param name="Base">Current <c>GodotObject</c></param>
|
||||
/// <param name="PropertyName">Property Name</param>
|
||||
/// <returns><c>bool</c> true if given property exists on Base</returns>
|
||||
public static bool HasProperty(this GodotObject Base, string PropertyName){
|
||||
/*
|
||||
Returns the object's property list as an Godot.Collections.Array of dictionaries.
|
||||
Each Godot.Collections.Dictionary contains the following entries:
|
||||
- name is the property's name, as a string;
|
||||
- class_name is an empty StringName, unless the property is Variant.Type.Object and it inherits from a class;
|
||||
- type is the property's type, as an int (see Variant.Type);
|
||||
- hint is how the property is meant to be edited (see PropertyHint);
|
||||
- hint_string depends on the hint (see PropertyHint);
|
||||
- usage is a combination of PropertyUsageFlags.
|
||||
*/
|
||||
foreach (var Property in Base.GetPropertyListEx())
|
||||
{
|
||||
if(Property.Name == PropertyName){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
public static (Node node, Resource resource, NodePath remaining) GetNodeAndResourceEx(this Node self, NodePath path)
|
||||
{
|
||||
var result = self.GetNodeAndResource(path);
|
||||
return ((Node)result[0], (Resource)result[1], (NodePath)result[2]);
|
||||
}
|
||||
public static void QueueFreeChildren(this Node node) => node.GetChildren().ToList().ForEach(item=>item.QueueFree());
|
||||
public static void QueueFreeChildren(this Node node, Func<Node,bool> predicate) => node.GetChildren().Where(predicate).ToList().ForEach(item=>item.QueueFree());
|
||||
}
|
||||
1
src/SjkScripts/SJKGodotHelpers/MyNodeExtensions.cs.uid
Normal file
1
src/SjkScripts/SJKGodotHelpers/MyNodeExtensions.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bklfdjfp02pav
|
||||
177
src/SjkScripts/SJKGodotHelpers/Raycasts/RaycastExtensions.cs
Normal file
177
src/SjkScripts/SJKGodotHelpers/Raycasts/RaycastExtensions.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using SJK.Functional;
|
||||
|
||||
namespace SJK.GodotHelpers.Raycasts;
|
||||
public record CollisionResultBase(
|
||||
GodotObject Collider,
|
||||
int ColliderId,
|
||||
Rid Rid,
|
||||
int Shape
|
||||
);
|
||||
|
||||
public record RaycastResult3D(
|
||||
GodotObject Collider,
|
||||
int ColliderId,
|
||||
Rid Rid,
|
||||
int Shape,
|
||||
Vector3 Position,
|
||||
Vector3 Normal
|
||||
) : CollisionResultBase(Collider, ColliderId, Rid, Shape);
|
||||
|
||||
public record RaycastResult2D(
|
||||
GodotObject Collider,
|
||||
int ColliderId,
|
||||
Rid Rid,
|
||||
int Shape,
|
||||
Vector2 Position,
|
||||
Vector2 Normal
|
||||
) : CollisionResultBase(Collider, ColliderId, Rid, Shape);
|
||||
|
||||
public record ShapeCastResult3D(
|
||||
GodotObject Collider,
|
||||
int ColliderId,
|
||||
Rid Rid,
|
||||
int Shape,
|
||||
Vector3 Point,
|
||||
Vector3 Normal,
|
||||
int CollisionCount
|
||||
) : CollisionResultBase(Collider, ColliderId, Rid, Shape);
|
||||
|
||||
public record ShapeCastResult2D(
|
||||
GodotObject Collider,
|
||||
int ColliderId,
|
||||
Rid Rid,
|
||||
int Shape,
|
||||
Vector2 Point,
|
||||
Vector2 Normal,
|
||||
int CollisionCount
|
||||
) : CollisionResultBase(Collider, ColliderId, Rid, Shape);
|
||||
|
||||
public record PointQueryResult3D(
|
||||
GodotObject Collider,
|
||||
int ColliderId,
|
||||
Rid Rid,
|
||||
int Shape
|
||||
) : CollisionResultBase(Collider, ColliderId, Rid, Shape);
|
||||
|
||||
public record PointQueryResult2D(
|
||||
GodotObject Collider,
|
||||
int ColliderId,
|
||||
Rid Rid,
|
||||
int Shape
|
||||
) : CollisionResultBase(Collider, ColliderId, Rid, Shape);
|
||||
|
||||
#nullable enable
|
||||
public static class RaycastExtensions
|
||||
{
|
||||
// --- 3D ---
|
||||
|
||||
public static RaycastResult3D? RaycastEx(this PhysicsDirectSpaceState3D space, Vector3 from, Vector3 to, uint collisionMask = uint.MaxValue, Rid[]? exclude = null, bool hitFromInside = false)
|
||||
{
|
||||
var query = new PhysicsRayQueryParameters3D
|
||||
{
|
||||
From = from,
|
||||
To = to,
|
||||
CollisionMask = collisionMask,
|
||||
HitFromInside = hitFromInside,
|
||||
};
|
||||
if (exclude != null)
|
||||
query.Exclude = new Array<Rid>(exclude);
|
||||
|
||||
var result = space.IntersectRay(query);
|
||||
if (result.Count == 0)
|
||||
return null;
|
||||
|
||||
return new RaycastResult3D(
|
||||
Collider: result["collider"].AsGodotObject(),
|
||||
ColliderId: result["collider_id"].AsInt32(),
|
||||
Rid: (Rid)result["rid"],
|
||||
Shape: result["shape"].AsInt32(),
|
||||
Position: result["position"].AsVector3(),
|
||||
Normal: result["normal"].AsVector3()
|
||||
);
|
||||
}
|
||||
|
||||
public static bool RaycastHitEx(this PhysicsDirectSpaceState3D space, Vector3 from, Vector3 to,[NotNullWhen(true)] out RaycastResult3D hit, uint collisionMask = uint.MaxValue, Rid[]? exclude = null, bool hitFromInside = false)
|
||||
{
|
||||
hit = space.RaycastEx(from, to, collisionMask, exclude, hitFromInside)!;
|
||||
return hit is not null;
|
||||
}
|
||||
|
||||
// --- 2D ---
|
||||
|
||||
public static RaycastResult2D? RaycastEx(this PhysicsDirectSpaceState2D space, Vector2 from, Vector2 to, uint collisionMask = uint.MaxValue, Rid[]? exclude = null, bool hitFromInside = false)
|
||||
{
|
||||
var query = new PhysicsRayQueryParameters2D
|
||||
{
|
||||
From = from,
|
||||
To = to,
|
||||
CollisionMask = collisionMask,
|
||||
HitFromInside = hitFromInside,
|
||||
};
|
||||
if (exclude != null)
|
||||
query.Exclude = new Array<Rid>(exclude);
|
||||
|
||||
var result = space.IntersectRay(query);
|
||||
if (result.Count == 0)
|
||||
return null;
|
||||
|
||||
return new RaycastResult2D(
|
||||
Collider: result["collider"].AsGodotObject(),
|
||||
ColliderId: result["collider_id"].AsInt32(),
|
||||
Rid: (Rid)result["rid"],
|
||||
Shape: result["shape"].AsInt32(),
|
||||
Position: result["position"].AsVector2(),
|
||||
Normal: result["normal"].AsVector2()
|
||||
);
|
||||
}
|
||||
public static IOption<RaycastResult2D> RaycastOptionEx(this PhysicsDirectSpaceState2D space, Vector2 from, Vector2 to, uint collisionMask = uint.MaxValue, Rid[]? exclude = null, bool hitFromInside = false)
|
||||
=> RaycastEx(space, from, to, collisionMask, exclude, hitFromInside).ToOption();
|
||||
|
||||
|
||||
public static bool RaycastHitEx(this PhysicsDirectSpaceState2D space, Vector2 from, Vector2 to, out RaycastResult2D hit, uint collisionMask = uint.MaxValue, Rid[]? exclude = null, bool hitFromInside = false)
|
||||
{
|
||||
hit = space.RaycastEx(from, to, collisionMask, exclude, hitFromInside)!;
|
||||
return hit is not null;
|
||||
}
|
||||
// 3D Shape Cast
|
||||
public static List<CollisionResultBase> IntersectShapeEx(this PhysicsDirectSpaceState3D space, PhysicsShapeQueryParameters3D query, int maxResults = 32)
|
||||
{
|
||||
var results = space.IntersectShape(query, maxResults);
|
||||
var list = new List<CollisionResultBase>(results.Count);
|
||||
|
||||
foreach (var dict in results)
|
||||
{
|
||||
list.Add(new CollisionResultBase(
|
||||
Collider: dict["collider"].AsGodotObject(),
|
||||
ColliderId: dict["collider_id"].AsInt32(),
|
||||
Rid: (Rid)dict["rid"],
|
||||
Shape: dict["shape"].AsInt32()
|
||||
));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
// 2D Shape Cast
|
||||
public static List<CollisionResultBase> IntersectShapeEx(this PhysicsDirectSpaceState2D space, PhysicsShapeQueryParameters2D query, int maxResults = 32)
|
||||
{
|
||||
var results = space.IntersectShape(query, maxResults);
|
||||
var list = new List<CollisionResultBase>(results.Count);
|
||||
|
||||
foreach (var dict in results)
|
||||
{
|
||||
list.Add(new CollisionResultBase(
|
||||
Collider: dict["collider"].AsGodotObject(),
|
||||
ColliderId: dict["collider_id"].AsInt32(),
|
||||
Rid: (Rid)dict["rid"],
|
||||
Shape: dict["shape"].AsInt32()
|
||||
));
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
uid://b8b5eyg6l31o1
|
||||
121
src/SjkScripts/SJKGodotHelpers/Reflector.cs
Normal file
121
src/SjkScripts/SJKGodotHelpers/Reflector.cs
Normal file
@@ -0,0 +1,121 @@
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Array = Godot.Collections.Array;
|
||||
namespace SJK.GodotHelpers;
|
||||
/// <summary>
|
||||
/// Strong‑typed view of Godot's get_method_list / get_property_list output.
|
||||
/// </summary>
|
||||
public static class Reflector
|
||||
{
|
||||
/* ──────────── Typed records ──────────── */
|
||||
|
||||
public readonly record struct ArgInfo(
|
||||
string Name,
|
||||
Variant.Type Type,
|
||||
PropertyHint Hint,
|
||||
string HintString,
|
||||
Variant DefaultValue);
|
||||
|
||||
public readonly record struct MethodInfoEx(
|
||||
string Name,
|
||||
IReadOnlyList<ArgInfo> Args,
|
||||
IReadOnlyList<Variant> DefaultArgs,
|
||||
MethodFlags Flags,
|
||||
int Id,
|
||||
ArgInfo? ReturnValue);
|
||||
|
||||
public readonly record struct PropertyInfoEx(
|
||||
string Name,
|
||||
string ClassName,
|
||||
Variant.Type Type,
|
||||
PropertyHint Hint,
|
||||
string HintString,
|
||||
PropertyUsageFlags Usage);
|
||||
|
||||
/* ──────────── Public helpers ──────────── */
|
||||
|
||||
public static List<MethodInfoEx> GetMethodsListEx(this GodotObject godotObject)=>GetMethods(godotObject);
|
||||
public static List<MethodInfoEx> GetMethods(GodotObject obj)
|
||||
{
|
||||
var raw = obj.GetMethodList();
|
||||
var list = new List<MethodInfoEx>(raw.Count);
|
||||
|
||||
foreach (Dictionary dict in raw)
|
||||
{
|
||||
// — Parse args —
|
||||
var argsRaw = (Array)dict["args"];
|
||||
var args = new List<ArgInfo>(argsRaw.Count);
|
||||
foreach (Dictionary a in argsRaw)
|
||||
args.Add(ParseArg(a));
|
||||
|
||||
// — Parse return (may be empty) —
|
||||
ArgInfo? ret = null;
|
||||
if (dict.TryGetValue("return", out var retRaw) && retRaw.AsGodotDictionary() is Dictionary rd && rd.Count > 0)
|
||||
ret = ParseArg(rd);
|
||||
|
||||
list.Add(new MethodInfoEx(
|
||||
Name: (string)dict["name"],
|
||||
Args: args,
|
||||
DefaultArgs: (Array)dict["default_args"],
|
||||
Flags: (MethodFlags)(int)dict["flags"],
|
||||
Id: (int)dict["id"],
|
||||
ReturnValue: ret
|
||||
));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
public static bool TryGetMethodInfo(this GodotObject godotObject, string property, out MethodInfoEx info){
|
||||
foreach (var item in GetMethods(godotObject))
|
||||
{
|
||||
if (item.Name == property){
|
||||
info = item;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
info = default;
|
||||
return false;
|
||||
}
|
||||
public static List<PropertyInfoEx> GetPropertyListEx(this GodotObject godotObject)=>GetProperties(godotObject);
|
||||
public static List<PropertyInfoEx> GetProperties(GodotObject obj)
|
||||
{
|
||||
var raw = obj.GetPropertyList();
|
||||
var list = new List<PropertyInfoEx>(raw.Count);
|
||||
|
||||
foreach (Dictionary dict in raw)
|
||||
{
|
||||
list.Add(new PropertyInfoEx(
|
||||
Name: (string)dict["name"],
|
||||
ClassName: (string)dict["class_name"],
|
||||
Type: (Variant.Type)(int)dict["type"],
|
||||
Hint: (PropertyHint)(int)dict["hint"],
|
||||
HintString: (string)dict["hint_string"],
|
||||
Usage: (PropertyUsageFlags)(int)dict["usage"]
|
||||
));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
public static PropertyInfoEx GetProperty(this IEnumerable<PropertyInfoEx> properties, string name)=>properties.FirstOrDefault(item=>item.Name == name);
|
||||
public static bool TryGetPropertyInfo(this GodotObject godotObject, string property, out PropertyInfoEx info){
|
||||
foreach (var item in GetProperties(godotObject))
|
||||
{
|
||||
if (item.Name == property){
|
||||
info = item;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
info = default;
|
||||
return false;
|
||||
}
|
||||
/* ──────────── Internals ──────────── */
|
||||
|
||||
private static ArgInfo ParseArg(Dictionary d) => new(
|
||||
Name: (string)d["name"],
|
||||
Type: (Variant.Type)(int)d["type"],
|
||||
Hint: (PropertyHint)(int)d["hint"],
|
||||
HintString: (string)d["hint_string"],
|
||||
DefaultValue: d.TryGetValue("default_value", out var dv) ? dv : default);
|
||||
|
||||
}
|
||||
1
src/SjkScripts/SJKGodotHelpers/Reflector.cs.uid
Normal file
1
src/SjkScripts/SJKGodotHelpers/Reflector.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bis0ef0hnuxin
|
||||
77
src/SjkScripts/SJKGodotHelpers/VariantUtils.cs
Normal file
77
src/SjkScripts/SJKGodotHelpers/VariantUtils.cs
Normal file
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
namespace SJK.GodotHelpers;
|
||||
public static class VariantUtils
|
||||
{
|
||||
public static Variant SafeToVariant(object value)
|
||||
{
|
||||
return value switch
|
||||
{
|
||||
null => new Variant(),
|
||||
bool b => Variant.From(b),
|
||||
int i => Variant.From(i),
|
||||
long l => Variant.From((int)l), // Godot Variant only supports 32-bit int
|
||||
float f => Variant.From(f),
|
||||
double d => Variant.From((float)d),
|
||||
string s => Variant.From(s),
|
||||
Vector2 v2 => Variant.From(v2),
|
||||
Vector2I v2i => Variant.From(v2i),
|
||||
Vector3 v3 => Variant.From(v3),
|
||||
Vector3I v3i => Variant.From(v3i),
|
||||
Vector4 v4 => Variant.From(v4),
|
||||
Vector4I v4i => Variant.From(v4i),
|
||||
Rect2 rect2 => Variant.From(rect2),
|
||||
Rect2I rect2i => Variant.From(rect2i),
|
||||
Quaternion q => Variant.From(q),
|
||||
Basis basis => Variant.From(basis),
|
||||
Transform2D t2d => Variant.From(t2d),
|
||||
Transform3D t3d => Variant.From(t3d),
|
||||
Color color => Variant.From(color),
|
||||
Plane plane => Variant.From(plane),
|
||||
Aabb aabb => Variant.From(aabb),
|
||||
GodotObject go => Variant.From(go),
|
||||
byte[] bytes => Variant.From(bytes),
|
||||
StringName sn => Variant.From(sn),
|
||||
NodePath np => Variant.From(np),
|
||||
Callable call => Variant.From(call),
|
||||
Signal sig => Variant.From(sig),
|
||||
Dictionary dict => Variant.From(dict),
|
||||
Godot.Collections.Array array => Variant.From(array),
|
||||
_ => throw new InvalidCastException($"Unsupported type '{value?.GetType().FullName}' for Variant conversion.")
|
||||
};
|
||||
}
|
||||
public static Type GetSystemType(this Variant.Type type) => type switch
|
||||
{
|
||||
Variant.Type.Nil => typeof(object),
|
||||
Variant.Type.Bool => typeof(bool),
|
||||
Variant.Type.Int => typeof(int),
|
||||
Variant.Type.Float => typeof(float),
|
||||
Variant.Type.String => typeof(string),
|
||||
Variant.Type.Vector2 => typeof(Vector2),
|
||||
Variant.Type.Vector2I => typeof(Vector2I),
|
||||
Variant.Type.Rect2 => typeof(Rect2),
|
||||
Variant.Type.Rect2I => typeof(Rect2I),
|
||||
Variant.Type.Vector3 => typeof(Vector3),
|
||||
Variant.Type.Vector3I => typeof(Vector3I),
|
||||
Variant.Type.Vector4 => typeof(Vector4),
|
||||
Variant.Type.Vector4I => typeof(Vector4I),
|
||||
Variant.Type.Transform2D => typeof(Transform2D),
|
||||
Variant.Type.Transform3D => typeof(Transform3D),
|
||||
Variant.Type.Basis => typeof(Basis),
|
||||
Variant.Type.Quaternion => typeof(Quaternion),
|
||||
Variant.Type.Aabb => typeof(Aabb),
|
||||
Variant.Type.Color => typeof(Color),
|
||||
Variant.Type.Plane => typeof(Plane),
|
||||
Variant.Type.StringName => typeof(StringName),
|
||||
Variant.Type.NodePath => typeof(NodePath),
|
||||
Variant.Type.Rid => typeof(Rid),
|
||||
Variant.Type.Object => typeof(GodotObject),
|
||||
Variant.Type.Callable => typeof(Callable),
|
||||
Variant.Type.Signal => typeof(Signal),
|
||||
Variant.Type.Dictionary => typeof(Godot.Collections.Dictionary),
|
||||
Variant.Type.Array => typeof(Godot.Collections.Array),
|
||||
_ => typeof(object)
|
||||
};
|
||||
}
|
||||
1
src/SjkScripts/SJKGodotHelpers/VariantUtils.cs.uid
Normal file
1
src/SjkScripts/SJKGodotHelpers/VariantUtils.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://b18a1dp6f8tsv
|
||||
132
src/VoxelGrid/BeltPort.cs
Normal file
132
src/VoxelGrid/BeltPort.cs
Normal file
@@ -0,0 +1,132 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Chickensoft.AutoInject;
|
||||
using Chickensoft.Introspection;
|
||||
using Godot;
|
||||
using SJK.Functional;
|
||||
|
||||
[Tool]
|
||||
[Meta(typeof(IAutoNode))]
|
||||
public partial class BeltPort : Node3D, IBeltPort {
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
[Dependency] public IVoxelGridRegistry Grid => this.DependOn<IVoxelGridRegistry>();
|
||||
[Export] public Direction Face { get; set; } = default!;
|
||||
[Export] public int Width { get; set; } = default!;
|
||||
[Export] public PortAccess Access { get; set; } = default!;
|
||||
[Export] public Path3D Path { get; set; } = default!;
|
||||
[Dependency] public IItemRenderer ItemRenderer => this.DependOn<IItemRenderer>();
|
||||
public BeltPortProfile Profile => new(GridTransform3D.FromGodot(GlobalTransform), Width, Access);
|
||||
[Dependency] public IItemTransferAnimator ItemTransferAnimator => this.DependOn<IItemTransferAnimator>(()=> new CurveItemTransfer(){Curve3D = Path.Curve,Tree = GetTree(), ItemRenderer = ItemRenderer, Transform3D = GlobalTransform});
|
||||
public void OnResolved()
|
||||
{
|
||||
if (Engine.IsEditorHint())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Grid.Register<IBeltPort>(this, [.. (this as IBeltPort).Points()]);
|
||||
}
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
DebugDraw3D.DrawLine(GlobalPosition,GlobalTransform * Face.ToVector(), Colors.Red);
|
||||
if (Engine.IsEditorHint())
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (int i = 0; i < _itemsDummys.Count; i++)
|
||||
{
|
||||
_itemsDummys[i] = (_itemsDummys[i].i+(float)delta,_itemsDummys[i].beltItem);
|
||||
GD.Print(_itemsDummys[i].i);
|
||||
ItemRenderer.UpdateTransform(_itemsDummys[i].beltItem,GlobalTransform * Path.Curve.SampleBakedWithRotation(_itemsDummys[i].i));
|
||||
if (_itemsDummys[i].i > Path.Curve.GetBakedLength())
|
||||
{
|
||||
_itemsDummys[i].beltItem.Dispose();
|
||||
_itemsDummys.RemoveAt(i);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool CanAccept(IBeltItem item, LaneSpan laneSpan, float beltT)
|
||||
{
|
||||
return true;
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
List<(float i,IBeltItem beltItem)> _itemsDummys = [];
|
||||
|
||||
public bool TryInsert(IBeltItem item, LaneSpan laneSpan, float beltT)
|
||||
{
|
||||
var tween = ItemTransferAnimator.StartTransfer(new(item,beltT){LaneSpan = laneSpan}, () => GD.Print("Done"));
|
||||
// GD.Print("gg "+laneSpan);
|
||||
tween.TweenCallback(Callable.From(item.Dispose));
|
||||
// GD.Print(laneSpan);
|
||||
// GD.Print(item.ToString());
|
||||
// _itemsDummys.Add((0,item));
|
||||
// item.Dispose();
|
||||
return true;
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
public interface IItemTransferAnimator
|
||||
{
|
||||
Tween StartTransfer(
|
||||
ConveyorSlice item,
|
||||
Action onFinished = null);
|
||||
}
|
||||
|
||||
public class InstanceItemTransfer : IItemTransferAnimator
|
||||
{
|
||||
public SceneTree Tree = default!;
|
||||
public Func<ConveyorSlice, bool> AcceptFunc = default!;
|
||||
public Tween StartTransfer(ConveyorSlice item, Action onFinished = null)
|
||||
{
|
||||
AcceptFunc(item);
|
||||
onFinished?.Invoke();
|
||||
return Tree.CreateTween();
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
public class CurveItemTransfer : IItemTransferAnimator
|
||||
{
|
||||
public Curve3D Curve3D = default!;
|
||||
public IItemRenderer ItemRenderer = default!;
|
||||
public Func<ConveyorSlice, bool> AcceptFunc = default!;
|
||||
public SceneTree Tree = default!;
|
||||
public Transform3D Transform3D;
|
||||
public Tween AnimateAlongCurve(
|
||||
ConveyorSlice item,
|
||||
Curve3D curve,
|
||||
float duration,
|
||||
IItemRenderer renderer)
|
||||
{
|
||||
float length = curve.GetBakedLength();
|
||||
|
||||
var tween = Tree.CreateTween();
|
||||
|
||||
tween.TweenMethod(
|
||||
Callable.From<float>(t =>
|
||||
{
|
||||
//Transform is not being set when using update transform in a tween, but can set position
|
||||
// renderer.UpdateTransform(item, Transform3D * curve.SampleBakedWithRotation(t),1/duration);
|
||||
(renderer as TestItemRendered)._items[item.Item].Transform =Transform3D *Curve3D.SampleBakedWithRotation(t).Translated(-Curve3D.SampleBakedWithRotation(t).Basis.X * item.LaneSpan.Start);
|
||||
// GD.PrintS(Transform3D * curve.SampleBakedWithRotation(t).Origin,1/duration,(renderer as TestItemRendered)._items[item].GlobalPosition);
|
||||
}),
|
||||
0f,
|
||||
length,
|
||||
duration
|
||||
);
|
||||
return tween;
|
||||
}
|
||||
public Tween StartTransfer(ConveyorSlice item, Action onFinished = null){
|
||||
var tween = AnimateAlongCurve(item,Curve3D,1,ItemRenderer);
|
||||
if (onFinished is not null){
|
||||
tween.TweenCallback(Callable.From(onFinished));
|
||||
}
|
||||
return tween;
|
||||
}
|
||||
}
|
||||
1
src/VoxelGrid/BeltPort.cs.uid
Normal file
1
src/VoxelGrid/BeltPort.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://ee5aoxi8mjnw
|
||||
114
src/VoxelGrid/ConveyorItemRender.cs
Normal file
114
src/VoxelGrid/ConveyorItemRender.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Chickensoft.AutoInject;
|
||||
using Chickensoft.Introspection;
|
||||
using Godot;
|
||||
|
||||
[Meta(typeof(IAutoNode))]
|
||||
public partial class ConveyorItemRender : Node
|
||||
{
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
[Export] protected Path3D Path3D { get; set; } = default!;
|
||||
[Export] protected TestItemConveyor ItemConveyor { get; set; } = default!;
|
||||
[Chickensoft.AutoInject.Dependency] protected IItemRenderer Items => this.DependOn<IItemRenderer>();
|
||||
private Chickensoft.Sync.Primitives.AutoList<ConveyorSlice>.Binding binding = default!;
|
||||
public override async void _Ready()
|
||||
{
|
||||
base._Ready();
|
||||
if (!ItemConveyor.IsNodeReady())
|
||||
{
|
||||
await ToSignal(ItemConveyor, Node.SignalName.Ready);
|
||||
}
|
||||
binding = ItemConveyor.Items.Items.Bind();
|
||||
// binding.OnRemove(callback =>
|
||||
// {
|
||||
// Items.Remove(callback.Item);
|
||||
// // GD.PrintS(callback.Item, callback.BeltT);
|
||||
// // if (itemsRenders.TryGetValue(callback.Item, out var node))
|
||||
// // {
|
||||
// // GD.PrintS(callback.Item, callback.BeltT,node);
|
||||
// // itemsRenders.Remove(callback.Item);
|
||||
// // node.QueueFree();
|
||||
// // }
|
||||
// });
|
||||
binding.OnAdd((i, v) =>
|
||||
{
|
||||
|
||||
// itemsRenders[i.Item] = new MeshInstance3D(){Mesh = new BoxMesh()};
|
||||
// AddSibling(itemsRenders[i.Item]);
|
||||
// itemsRenders[i.Item].Transform = Path3D.Curve.SampleBakedWithRotation(i.BeltT);
|
||||
// GD.Print($"Item : {i},{v} added");
|
||||
Items.UpdateTransform(i.Item,Path3D.GlobalTransform *Path3D.Curve.SampleBakedWithRotation(i.BeltT));
|
||||
});
|
||||
binding.OnUpdate((a,b) =>
|
||||
{
|
||||
Items.UpdateTransform(a.Item, Path3D.GlobalTransform *Path3D.Curve.SampleBakedWithRotation(b.BeltT).Translated(-Path3D.Curve.SampleBakedWithRotation(b.BeltT).Basis.X*(b.LaneSpan.Start)));
|
||||
return;
|
||||
if (itemsRenders.TryGetValue(a.Item, out var node))
|
||||
{
|
||||
if (tweens.TryGetValue(a.Item, out var t))
|
||||
{
|
||||
t.Kill();
|
||||
}
|
||||
tweens[a.Item] = t = CreateTween();
|
||||
t.TweenProperty(node, "transform",Path3D.Curve.SampleBakedWithRotation(b.BeltT), .25f);
|
||||
// node.Transform = Path3D.Curve.SampleBakedWithRotation(b.BeltT);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
private Dictionary<IBeltItem, Node3D> itemsRenders = [];//This would be an item server for reuse via depency
|
||||
private Dictionary<IBeltItem, Tween> tweens = [];//This would be an item server for reuse via depency
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
binding.Dispose();
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IItemRenderer
|
||||
{
|
||||
// Node3D GetVisualNode(IBeltItem beltItem);
|
||||
void UpdateTransform(IBeltItem beltItem, Transform3D newTransform);
|
||||
void UpdateTransform(IBeltItem beltItem, Transform3D newTransform, float time);
|
||||
void Remove(IBeltItem beltItem);
|
||||
}
|
||||
public partial class TestItemRendered : Node3D, IItemRenderer
|
||||
{
|
||||
public Dictionary<IBeltItem, Node3D> _items = [];
|
||||
private ConditionalWeakTable<IBeltItem, Tween> _tweens = [];
|
||||
public void Remove(IBeltItem beltItem)
|
||||
{
|
||||
if (_items.TryGetValue(beltItem, out var node)){
|
||||
node.QueueFree();
|
||||
}
|
||||
_items.Remove(beltItem);
|
||||
}
|
||||
|
||||
public void UpdateTransform(IBeltItem beltItem, Transform3D newTransform, float time ){
|
||||
if (!_items.TryGetValue(beltItem, out var node))
|
||||
{
|
||||
_items.Add(beltItem,node = beltItem.CreateItemVisual());
|
||||
AddChild(node);
|
||||
beltItem.Disposed += _ =>{node.QueueFree();_items.Remove(beltItem);};
|
||||
node.Transform = newTransform;
|
||||
return;
|
||||
}
|
||||
if (_tweens.TryGetValue(beltItem, out var tween))
|
||||
{
|
||||
tween.Kill();
|
||||
_tweens.Remove(beltItem);
|
||||
}
|
||||
// GD.Print(newTransform);
|
||||
tween = GetTree().CreateTween().BindNode(node);
|
||||
tween.TweenProperty(node, "transform", newTransform, time);
|
||||
_tweens.Add(beltItem,tween);
|
||||
}
|
||||
|
||||
public void UpdateTransform(IBeltItem beltItem, Transform3D newTransform) => UpdateTransform(beltItem,newTransform,.25f);
|
||||
}
|
||||
1
src/VoxelGrid/ConveyorItemRender.cs.uid
Normal file
1
src/VoxelGrid/ConveyorItemRender.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bjuntmf2sjynp
|
||||
83
src/VoxelGrid/Equipment.cs
Normal file
83
src/VoxelGrid/Equipment.cs
Normal file
@@ -0,0 +1,83 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using System;
|
||||
using Chickensoft.AutoInject;
|
||||
using Chickensoft.GodotNodeInterfaces;
|
||||
using Chickensoft.Introspection;
|
||||
using Godot;
|
||||
public interface IEquipment : IProvide<IEquipmentContext>//INode3D,
|
||||
{
|
||||
EquipmentId Id { get; set; }
|
||||
}
|
||||
[Tool]
|
||||
[Meta(typeof(IAutoNode))]
|
||||
public partial class Equipment() : Node3D, IEquipment
|
||||
{
|
||||
|
||||
public override void _Notification(int what)
|
||||
{
|
||||
if (what == NotificationTransformChanged)
|
||||
{
|
||||
OnNotificationTransformChanged();
|
||||
}
|
||||
// if (Engine.IsEditorHint())
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
this.Notify(what);
|
||||
}
|
||||
|
||||
public EquipmentId Id { get; set; } = new(Guid.NewGuid());
|
||||
[Signal] public delegate void TickEventHandler();
|
||||
[Export]public Vector3I GridPos { get; set; }
|
||||
[Dependency] protected IVoxelGridQuery<LayeredEquipment> Grid => this.DependOn<IVoxelGridQuery<LayeredEquipment>>();
|
||||
|
||||
protected IEquipmentContext _equipmentContext { get; set; } = default!;
|
||||
public override void _Ready()
|
||||
{
|
||||
this.SetNotifyTransform(true);
|
||||
base._Ready();
|
||||
// if (Engine.IsEditorHint())
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
_equipmentContext = new eqitpTest(this);
|
||||
this.Provide();
|
||||
Timer timer = new Timer(){WaitTime = .25f, Autostart = true};
|
||||
AddChild(timer);
|
||||
timer.Timeout += EmitSignalTick;
|
||||
// timer.Timeout += () => Position += Vector3.One;
|
||||
}
|
||||
|
||||
public void OnResolved()
|
||||
{
|
||||
GD.Print(Grid);
|
||||
(Grid as EquipmentVoxelGrid).AddEquipment(GridPos, this);
|
||||
GD.Print("Added Self");
|
||||
}
|
||||
IEquipmentContext IProvide<IEquipmentContext>.Value() => _equipmentContext;
|
||||
public void OnNotificationTransformChanged()
|
||||
{
|
||||
GD.Print("Transform changed, now" , Position);
|
||||
var newPos = new Vector3I(Mathf.RoundToInt(Position.X),Mathf.RoundToInt(Position.Y),Mathf.RoundToInt(Position.Z));
|
||||
if (newPos == GridPos)
|
||||
{
|
||||
return;
|
||||
}
|
||||
GridPos = newPos;
|
||||
// Transform = Transform.Origin = newPos;;
|
||||
}
|
||||
}
|
||||
public record EquipmentId(Guid Id);
|
||||
public interface IEquipmentContext
|
||||
{
|
||||
|
||||
Equipment GetEquipment();
|
||||
EquipmentId GetEquipmentId();
|
||||
|
||||
}
|
||||
public record eqitpTest(Equipment Equipment) : IEquipmentContext
|
||||
{
|
||||
public Equipment GetEquipment() => Equipment;
|
||||
public EquipmentId GetEquipmentId() => Equipment.Id;
|
||||
}
|
||||
1
src/VoxelGrid/Equipment.cs.uid
Normal file
1
src/VoxelGrid/Equipment.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://bhh1c4a5gep6o
|
||||
788
src/VoxelGrid/ItemConveyor.cs
Normal file
788
src/VoxelGrid/ItemConveyor.cs
Normal file
@@ -0,0 +1,788 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.AutoInject;
|
||||
using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Chickensoft.Sync.Primitives;
|
||||
using SJK.Functional;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
public interface IMovementConveyor
|
||||
{
|
||||
IBeltPort StartPort { get; }
|
||||
// IBeltSlotProfile StartPort { get; }
|
||||
IBeltPort EndPort { get; }
|
||||
// IBeltSlotProfile EndPort { get; }
|
||||
// IOption<IBeltSlotProfile> InputPort { get; }
|
||||
// IOption<IBeltSlotProfile> OutputPort { get; }
|
||||
// IEnumerable<IBeltSlotProfile> GetPorts();
|
||||
IEnumerable<IBeltPort> GetPorts();
|
||||
IAutoValue<float> SpeedValue { get; }
|
||||
float SpeedMagnitude { get; set; }
|
||||
float SignedSpeed { get; set; }
|
||||
bool IsReversed { get; set; }
|
||||
float Length {get;set;}
|
||||
// float GetAvailableTravel(ItemConveyor.BeltDirection beltDirection, LaneSpanT itemSpan, float maxDistance);
|
||||
// IBeltSlotProfile GetPortFacingStart();
|
||||
// IBeltSlotProfile GetPortFacingEnd();
|
||||
ItemConveyor.BeltDirection GetBeltDirection();
|
||||
// Option<ConveyorSlice> GetItemTowardStart();
|
||||
// Option<ConveyorSlice> GetItemTowardEnd();
|
||||
// Option<ConveyorSlice> GetItemTowardInput();
|
||||
// Option<ConveyorSlice> GetItemTowardOutput();
|
||||
IEnumerable<Sorted1DList<ConveyorSlice>.ItemHandle> EnumerateTowardEnd();
|
||||
IEnumerable<Sorted1DList<ConveyorSlice>.ItemHandle> EnumerateTowardStart();
|
||||
ItemConveyor.IBeltMovement GetMovementPolicy();
|
||||
// IList<ConveyorSlice> Items { get; }
|
||||
IOption<IBeltPort> GetPortFacing(IBeltPort slot);
|
||||
BeltObstacle GetDistanceToNextItem(ItemConveyor.BeltDirection beltDirection, float itemBeltT, float maxDistToCheck, LaneSpan laneSpan, HashSet<IMovementConveyor>? visted = null);
|
||||
ConveyorPort CreatePort(BeltPortProfile profile, ItemConveyor.BeltT beltT, LaneSpan laneSpan);
|
||||
}
|
||||
public readonly struct ConveyorItemHandle
|
||||
{
|
||||
private readonly Action _remove;
|
||||
private readonly Action<ConveyorSlice> _replace;
|
||||
public int Index { get; }
|
||||
public ConveyorSlice Slice { get; }
|
||||
public IBeltItem Item => Slice.Item;
|
||||
public LaneSpan Span => Slice.LaneSpan;
|
||||
public float BeltT => Slice.BeltT;
|
||||
public ConveyorItemHandle(int index, ConveyorSlice slice, Action remove, Action<ConveyorSlice> replace)
|
||||
{
|
||||
Index = index;
|
||||
Slice = slice;
|
||||
_remove = remove;
|
||||
_replace = replace;
|
||||
}
|
||||
public void Remove() => _remove();
|
||||
public void Replace(ConveyorSlice slice) => _replace(slice);
|
||||
}
|
||||
[Meta(typeof(IAutoNode))]
|
||||
public partial class ItemConveyor : Node, IVoxelNode
|
||||
{
|
||||
// public record ItemPair(IBeltItem Item, float BeltT);
|
||||
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
[Export] public int Length { get; set; } = 1;
|
||||
// [Export] public float Speed { get; set; } = .05f;
|
||||
private readonly AutoValue<float> _speed = new(.05f);
|
||||
public IAutoValue<float> Speed => _speed;
|
||||
public float SignedSpeed
|
||||
{
|
||||
get => _speed.Value;
|
||||
set => _speed.Value = value;
|
||||
}
|
||||
|
||||
public float SpeedMagnitude
|
||||
{
|
||||
get => Mathf.Abs(_speed.Value);
|
||||
set => _speed.Value = Mathf.Abs(value) * Mathf.Sign(_speed.Value);
|
||||
}
|
||||
public bool IsReversed { get => Mathf.Sign(_speed.Value) < 0; set => _speed.Value = SpeedMagnitude * (value ? -1 : 1); }
|
||||
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>();
|
||||
[Dependency] public IBeltMovement MovementSystem => this.DependOn<IBeltMovement>(() => new IndividualMovement());
|
||||
public const float ITEMSIZE = .2f;
|
||||
private const float ITEMHALFSIZE = ITEMSIZE / 2f;
|
||||
//Basci port for simple conveyor, asuming no rever,
|
||||
private List<ConveyorPort> _ports = [
|
||||
new(){//EjectFace
|
||||
Face = Direction.Front,
|
||||
Direction= PortAccess.InOut,
|
||||
LocalOffset = Vector3I.Zero,
|
||||
BeltT = new BeltTEnd(ConveyorEnd.End),
|
||||
PullPush = TransferMode.PushPull,
|
||||
},
|
||||
new(){//PullFace
|
||||
Face = Direction.Back,
|
||||
Direction= PortAccess.InOut,
|
||||
LocalOffset = Vector3I.Zero,
|
||||
BeltT = new BeltTEnd(ConveyorEnd.Start),
|
||||
PullPush = TransferMode.PushPull//Or none, PlateUp has grtabber and none grabby varietns
|
||||
},
|
||||
new(){//PullFace
|
||||
Face = Direction.Right,
|
||||
Direction= PortAccess.BiDirectional,
|
||||
LocalOffset = Vector3I.Zero,
|
||||
BeltT = new BeltTOffset(.5f),
|
||||
},
|
||||
new(){//PullFace
|
||||
Face = Direction.Left,
|
||||
Direction= PortAccess.BiDirectional,
|
||||
LocalOffset = Vector3I.Zero,
|
||||
BeltT = new BeltTOffset(.5f),
|
||||
},
|
||||
new(){//PullFace
|
||||
Face = Direction.Up,
|
||||
Direction= PortAccess.BiDirectional,
|
||||
LocalOffset = Vector3I.Zero,
|
||||
BeltT = new BeltTOffset(.5f),
|
||||
}
|
||||
];
|
||||
public ConveyorPort GetInputPort() => _ports.First(item => (item.PullPush & TransferMode.Pull)>0 && item.BeltT == (SignedSpeed >= 0 ? new BeltTEnd(ConveyorEnd.Start):new BeltTEnd(ConveyorEnd.End)));//Cache this
|
||||
public ConveyorPort GetOutputPort() => _ports.First(item => (item.PullPush & TransferMode.Push)>0 && item.BeltT == (SignedSpeed < 0 ? new BeltTEnd(ConveyorEnd.Start):new BeltTEnd(ConveyorEnd.End)));//Cache this
|
||||
public ConveyorPort GetStartPort() => _ports.First(item => item.BeltT == new BeltTEnd(ConveyorEnd.Start));
|
||||
public ConveyorPort GetEndPort() => _ports.First(item => item.BeltT == new BeltTEnd(ConveyorEnd.End));
|
||||
// public ConveyorPort GetPortInDirectionOfTravel() => Speed >= 0 ? GetOutputPort() : GetInputPort();
|
||||
//needs better name
|
||||
// private IEnumerable<ConveyorPort> ActivePorts(TransferMode p) => _ports.Where(item => item.PullPush == p);
|
||||
public void OnResolved()
|
||||
{
|
||||
// GD.Print(SlotExtesion.MapSlotToFacingSlot(Vector3I.Zero,Direction.Back,2,new(2,0,1),Direction.Front,3));
|
||||
// GD.Print(SlotExtesion.MapSlotToFacingSlot(new(2,0,1),Direction.Front,3,Vector3I.Zero,Direction.Back,2));
|
||||
GridRegistry.Register(this);
|
||||
Timer timer = new Timer() { WaitTime = .25f, Autostart = true };//TEST
|
||||
AddChild(timer);//TEST
|
||||
timer.Timeout += OnTick;//TEST
|
||||
for (int i = 0; i < _ports.Count; i++)
|
||||
{
|
||||
var port = _ports[i];
|
||||
if (port.Access.HasFlag(PortAccess.In)){
|
||||
port.AcceptItemFunc = (item, belt) =>
|
||||
{
|
||||
GD.PrintS("============================",port.Access,item,belt,port.BeltT);
|
||||
if (port.BeltT is BeltTEnd end)
|
||||
{
|
||||
return TryInsertAtEnd(end.End, item);
|
||||
}
|
||||
if (port.BeltT is BeltTOffset offset && TryFindLocalInsertion(offset.T,ITEMHALFSIZE,out var point))
|
||||
{
|
||||
InsertInMiddleWithoutCheck(item, point);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
_ports[i] = port;
|
||||
}
|
||||
}
|
||||
|
||||
// MovementSystem.OnItemMoved += static (item, segments, delta) => GD.PrintS(item, string.Join(',', segments.ToArray().Select(i => $"({i.Conveyor},{i.StartT},{i.EndT}")));
|
||||
}
|
||||
public override void _ExitTree()
|
||||
{
|
||||
base._ExitTree();
|
||||
GridRegistry.UnRegister(Id);
|
||||
}
|
||||
public void OnTick()//TEMP METHOOD FOR TESTING
|
||||
{
|
||||
if (Input.IsActionPressed("ui_up"))
|
||||
{
|
||||
SignedSpeed = -SignedSpeed;
|
||||
}
|
||||
if (VoxelPosition == Vector3I.Forward*-2 && TryInsertAtEnd(ConveyorEnd.Start, new TestItem(){Height = 1, Width = 1, Temp = 1}))
|
||||
InsertAtEndWithoutCheck(ConveyorEnd.Start,new TestItem(){Height = 1, Width = 1, Temp = 1});
|
||||
MovementSystem.AdvanceBelt(this,1f);
|
||||
return;
|
||||
// if (_items.Count <= 0)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// if (Speed == 0)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// GD.Print("Thing: " + Speed + " "+ GetConveyorPortFacing(GetOutputPort()));
|
||||
// var distToEnd = DistanceFromEnd(Speed < 0 ? ConveyorEnd.Start : ConveyorEnd.End);
|
||||
// var maxSpeed = Mathf.Min(Mathf.Abs(Speed), distToEnd) * Mathf.Sign(Speed);
|
||||
|
||||
// if (maxSpeed == 0)
|
||||
// {
|
||||
// return;
|
||||
// }
|
||||
// // GD.Print()
|
||||
// for (int i = 0; i < _items.Count; i++)
|
||||
// {
|
||||
// _items[i] = _items[i] with { BeltT = Mathf.Clamp(_items[i].BeltT + maxSpeed, 0, Length) };
|
||||
// GD.Print($"Item:{_items[i].Item}, Position:{_items[i].BeltT}");
|
||||
// }
|
||||
// // _items.ForEach((item) => item = item = Mathf.Clamp(item.Position + maxSpeed, 0, Length));
|
||||
// var list = _items.Where(item=> Speed>=0?item.BeltT >= Length:item.BeltT <= 0).ToList();
|
||||
// // GD.Print(list.Count);
|
||||
// foreach (var item in list)
|
||||
// {
|
||||
// GD.Print($"Item:{item.Item}, Position:{item.BeltT}");
|
||||
// //TEST
|
||||
// var v = GetConveyorPortFacing(GetOutputPort());
|
||||
// if (v.HasValue)
|
||||
// {
|
||||
// GD.PrintS(v.Value.Port.LocalOffset,v.Value.Port.BeltT,v.Value.Port.Direction,v.Value.Port.PullPush,v.Value.Port.Face);
|
||||
// if(v.Value.Conveyor.TryInsertAtEnd((v.Value.Port.BeltT as BeltTEnd).End, item.Item))
|
||||
// {
|
||||
// GD.Print("Item Moved");
|
||||
// _items.Remove(item);
|
||||
// }
|
||||
|
||||
// }
|
||||
// //Move Item Into Other COnveyor with offset, this means a item could therolitcly move two the end of another conveyor, but need to decide if that should keep moving recurively, opr make items only move one conveyor max at atime
|
||||
// }
|
||||
|
||||
}
|
||||
public float? GetAvailableTravelForFrontItem(bool accountForNextConveyor = false) => GetBeltDirection() switch
|
||||
{
|
||||
BeltDirection.TowardStart => AnyItems() ? GetAvailableTravel(0, BeltDirection.TowardStart, accountForNextConveyor) : null,
|
||||
BeltDirection.TowardEnd => AnyItems() ? GetAvailableTravel(ItemsCount - 1, BeltDirection.TowardEnd, accountForNextConveyor) : null,
|
||||
BeltDirection.NotMoving => null,
|
||||
_ => throw new NotSupportedException(),
|
||||
};
|
||||
public float GetAvailableSpaceFromEnd(ConveyorEnd end) => end switch
|
||||
{
|
||||
ConveyorEnd.Start => AnyItems()?_items[0].BeltT - ITEMSIZE:Length -ITEMHALFSIZE,
|
||||
ConveyorEnd.End => AnyItems()?_items[^1].BeltT + ITEMSIZE : ITEMHALFSIZE,
|
||||
_ => throw new NotSupportedException(),
|
||||
};
|
||||
public float GetAvailableTravel(
|
||||
int itemIndex,
|
||||
BeltDirection direction,
|
||||
bool accountForNextConveyor = false
|
||||
)
|
||||
{
|
||||
if (itemIndex < 0 || itemIndex >= _items.Count)
|
||||
throw new IndexOutOfRangeException(
|
||||
$"{nameof(itemIndex)}={itemIndex}, Count={_items.Count}"
|
||||
);
|
||||
var item = _items[itemIndex];
|
||||
|
||||
// Determine neighbor and boundary
|
||||
bool towardEnd = direction == BeltDirection.TowardEnd;
|
||||
|
||||
float limit;
|
||||
|
||||
if (towardEnd)
|
||||
{
|
||||
var other = GetConveyorPortFacing(GetEndPort());
|
||||
// Next item or belt end
|
||||
limit = (itemIndex + 1 < _items.Count)
|
||||
? _items[itemIndex + 1].BeltT - ITEMSIZE
|
||||
: Length + (accountForNextConveyor && other.HasValue && other.Value.Port.BeltT is BeltTEnd end ? other.Value.Conveyor.GetAvailableSpaceFromEnd(end.End) : -ITEMHALFSIZE);
|
||||
}
|
||||
else
|
||||
{
|
||||
var other = GetConveyorPortFacing(GetStartPort());
|
||||
// Previous item or belt start
|
||||
limit = (itemIndex - 1 >= 0)
|
||||
? _items[itemIndex - 1].BeltT + ITEMSIZE
|
||||
: accountForNextConveyor && other.HasValue && other.Value.Port.BeltT is BeltTEnd end ? other.Value.Conveyor.GetAvailableSpaceFromEnd(end.End) : ITEMHALFSIZE;
|
||||
}
|
||||
|
||||
float available = towardEnd
|
||||
? limit - item.BeltT
|
||||
: item.BeltT - limit;
|
||||
|
||||
return Mathf.Max(0f, available);
|
||||
}
|
||||
|
||||
public enum ConveyorEnd { Start, End }
|
||||
public static ConveyorEnd SwapEnd(ConveyorEnd end) => end switch
|
||||
{
|
||||
ConveyorEnd.Start => ConveyorEnd.End,
|
||||
ConveyorEnd.End => ConveyorEnd.Start,
|
||||
_ => throw new NotSupportedException($"{end}"),
|
||||
};
|
||||
public float DistanceFromEnd(ConveyorEnd end) => end switch
|
||||
{
|
||||
ConveyorEnd.Start => _items.Count == 0 ? Length : _items[0].BeltT,
|
||||
ConveyorEnd.End => _items.Count == 0 ? Length : Length - _items[^1].BeltT,
|
||||
_ => throw new NotSupportedException($"{end}"),
|
||||
};
|
||||
public float GetTravelDistanceInDirection(BeltDirection direction) => direction switch
|
||||
{
|
||||
BeltDirection.TowardEnd => _items.Count == 0 ? Length : _items[0].BeltT,
|
||||
BeltDirection.TowardStart => _items.Count == 0 ? Length : Length - _items[^1].BeltT,
|
||||
BeltDirection.NotMoving => 0,
|
||||
_ => throw new NotSupportedException($"{direction}"),
|
||||
};
|
||||
public enum BeltDirection : sbyte {
|
||||
TowardStart = -1,
|
||||
NotMoving = 0,
|
||||
TowardEnd = 1
|
||||
}
|
||||
public bool TryFindLocalInsertion(
|
||||
float beltT,
|
||||
float maxDistance,
|
||||
out float resultT
|
||||
)
|
||||
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
// float minAllowed = Mathf.Max(ITEMHALFSIZE, beltT - maxDistance);
|
||||
// float maxAllowed = Mathf.Min(Length - ITEMHALFSIZE, beltT + maxDistance);
|
||||
|
||||
// if (minAllowed > maxAllowed)
|
||||
// {
|
||||
// resultT = default;
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// if (_items.Count == 0)
|
||||
// {
|
||||
// resultT = Mathf.Clamp(beltT, minAllowed, maxAllowed);
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// int index = _items.BinarySearch(
|
||||
// new(null!, beltT),
|
||||
// Comparer<ConveyorSlice>.Create(
|
||||
// (a, b) => a.BeltT.CompareTo(b.BeltT)
|
||||
// )
|
||||
// );
|
||||
|
||||
// if (index < 0)
|
||||
// {
|
||||
// index = ~index;
|
||||
// }
|
||||
|
||||
// float gapMin = index > 0
|
||||
// ? _items[index - 1].BeltT + ITEMSIZE
|
||||
// : ITEMHALFSIZE;
|
||||
|
||||
// float gapMax = index < _items.Count
|
||||
// ? _items[index].BeltT - ITEMSIZE
|
||||
// : Length - ITEMHALFSIZE;
|
||||
|
||||
// // Intersect gap with allowed window
|
||||
// gapMin = Mathf.Max(gapMin, minAllowed);
|
||||
// gapMax = Mathf.Min(gapMax, maxAllowed);
|
||||
|
||||
// if (gapMin > gapMax)
|
||||
// {
|
||||
// resultT = default;
|
||||
// return false;
|
||||
// }
|
||||
|
||||
// resultT = Mathf.Clamp(beltT, gapMin, gapMax);
|
||||
// return true;
|
||||
}
|
||||
public BeltDirection GetBeltDirection() => _speed.Value switch
|
||||
{
|
||||
0 => BeltDirection.NotMoving,
|
||||
> 0 => BeltDirection.TowardEnd,
|
||||
< 0 => BeltDirection.TowardStart,
|
||||
_ => throw new NotSupportedException($"{nameof(Speed)} with value {Speed} is not Supported")
|
||||
};
|
||||
/// <summary>
|
||||
/// Returns the Item that would be Leading the Belt based off the direction of the belt, or null if no items or Speed is zero.
|
||||
/// </summary>
|
||||
/// <returns>The Leading Item</returns> <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ConveyorSlice? GetFirstItem() => GetBeltDirection() switch
|
||||
{
|
||||
BeltDirection.TowardStart => _items.Any() ? _items[0] : null,
|
||||
BeltDirection.TowardEnd => _items.Any() ? _items[^1] : null,
|
||||
BeltDirection.NotMoving => null,
|
||||
_ => null
|
||||
};
|
||||
/// <summary>
|
||||
/// Returns the Last item that would be oppsite the Leading Item, or null if there is no items or Speed is zero.
|
||||
/// </summary>
|
||||
/// <returns>The last Item in Secuance</returns> <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public ConveyorSlice? GetLastItem() => GetBeltDirection() switch
|
||||
{
|
||||
BeltDirection.TowardStart => _items.Any() ? _items[0] : null,
|
||||
BeltDirection.TowardEnd => _items.Any() ? _items[^1] : null,
|
||||
BeltDirection.NotMoving => null,
|
||||
_ => null
|
||||
};
|
||||
public bool AnyItems() => _items.Any();
|
||||
public int ItemsCount => _items.Count;
|
||||
private void InsertInMiddleWithoutCheck(IBeltItem item, float beltT)
|
||||
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
_items.Add(new(item, beltT));
|
||||
|
||||
// _items.Sort(Comparer<ConveyorSlice>.Create(
|
||||
// (a, b) => a.BeltT.CompareTo(b.BeltT)));
|
||||
}
|
||||
|
||||
// public float SpaceToInsertFromEnd(ConveyorEnd end)//Needs to know if belt space exists in next/previous
|
||||
// {
|
||||
// var distToEnd = DistanceFromEnd(end);
|
||||
// var other = GetConveyorPortFacing(GetOutputPort());
|
||||
// if (!other.HasValue || other.Value.Port.BeltT != new BeltTEnd(SwapEnd(end)) || other.Value.Port.Direction == PortAccess.Out)
|
||||
// {
|
||||
// return Mathf.Max(0, distToEnd - ITEMHALFSIZE);
|
||||
// }
|
||||
|
||||
// return distToEnd + other.Value.Conveyor.DistanceFromEnd(SwapEnd(end));//Should Be space to inset, but needs to be serpate into 2 functons to prevent stgackoverflow
|
||||
|
||||
// }
|
||||
public bool TryInsertAtEnd(ConveyorEnd end, IBeltItem itemStack)
|
||||
{
|
||||
var space = DistanceFromEnd(end);
|
||||
if (space >= ITEMHALFSIZE)
|
||||
{
|
||||
InsertAtEndWithoutCheck(end, itemStack);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private void InsertAtEndWithoutCheck(ConveyorEnd end, IBeltItem item, float offset = 0)
|
||||
{
|
||||
ConveyorSlice itemPair = new(item, end switch { ConveyorEnd.Start => offset, ConveyorEnd.End => Length - offset, _ => throw new NotSupportedException() });
|
||||
if (end == ConveyorEnd.Start)
|
||||
{
|
||||
_items.Insert(0, itemPair);
|
||||
}
|
||||
else
|
||||
{
|
||||
_items.Add(itemPair);
|
||||
}
|
||||
}
|
||||
private (ItemConveyor Conveyor, ConveyorPort Port)? GetConveyorPortFacing(ConveyorPort port, Predicate<ConveyorPort>? predicate = default)
|
||||
{
|
||||
predicate ??= _ => true;
|
||||
var pos = VoxelPosition + port.LocalOffset + port.Face.ToVector();
|
||||
var others = GridRegistry.Get<ItemConveyor>(pos);
|
||||
foreach (var item in others)
|
||||
{
|
||||
var otherPorts = item._ports.Where(otherPort => otherPort.Face.Reverse() == port.Face && predicate(otherPort));
|
||||
GD.Print("ports "+string.Join(',',otherPorts.Select(i=>i.BeltT)));
|
||||
if (otherPorts.Any())
|
||||
{
|
||||
return (item, otherPorts.First());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public Guid Id { get; set; }
|
||||
[Export]
|
||||
public Vector3I VoxelPosition { get; set; }
|
||||
[Export]
|
||||
public Direction VoxelRotation { get; set; } = Direction.Front;//test
|
||||
|
||||
public IEnumerable<Vector3I> Shape => [Vector3I.Zero];
|
||||
|
||||
private readonly AutoList<ConveyorSlice> _items = [];
|
||||
public IAutoList<ConveyorSlice> Items => _items;
|
||||
public struct ConveyorPort : IBeltSlotProfile// May chagne to record
|
||||
{
|
||||
public Vector3I LocalOffset;
|
||||
public BeltT BeltT;
|
||||
public Direction Face;
|
||||
public TransferMode PullPush;
|
||||
public PortAccess Direction;
|
||||
public Func<IBeltItem, float, bool>? AcceptItemFunc;
|
||||
public Func<IBeltItem, float, bool>? CanAcceptItemFunc;
|
||||
public IMovementConveyor MovementConveyor;
|
||||
public readonly Vector3I Position => LocalOffset;
|
||||
|
||||
public readonly int Width => 1;
|
||||
|
||||
public readonly PortAccess Access => Direction;
|
||||
|
||||
public Guid Id => throw new NotImplementedException();
|
||||
|
||||
public Vector3I VoxelPosition => LocalOffset;
|
||||
|
||||
public Direction VoxelRotation => throw new NotImplementedException();
|
||||
|
||||
public IEnumerable<Vector3I> Shape => [new Vector3I(0, 0, 0)];
|
||||
|
||||
// readonly Direction IBeltSlotProfile.Direction => Face;
|
||||
|
||||
[MemberNotNullWhen(true,nameof(CanAcceptItemFunc))]
|
||||
[MemberNotNullWhen(true,nameof(AcceptItemFunc))]
|
||||
public readonly bool CanAcceptItem(IBeltItem beltItem, LaneSpan laneSpan, float beltT = 0)
|
||||
{
|
||||
if (CanAcceptItemFunc is null || AcceptItemFunc is null || !Access.HasFlag(PortAccess.In) )//|| LaneSpan.Encapsulates(new(0, (ushort)Width), laneSpan))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return CanAcceptItemFunc(beltItem, beltT);
|
||||
}
|
||||
|
||||
|
||||
public readonly bool TryInsertItem(IBeltItem beltItem, LaneSpan laneSpan, float beltT = 0)
|
||||
{
|
||||
if (!CanAcceptItem(beltItem, laneSpan, beltT))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return AcceptItemFunc(beltItem, beltT);
|
||||
}
|
||||
}
|
||||
public abstract record BeltT();
|
||||
public record BeltTEnd(ConveyorEnd End) : BeltT();
|
||||
public record BeltTOffset(float T) : BeltT();
|
||||
// public enum PortAccess : byte
|
||||
// {
|
||||
// In,
|
||||
// Out,
|
||||
// InOut,
|
||||
// BiDirectional = InOut
|
||||
// }
|
||||
// [Flags]
|
||||
// public enum TransferMode : byte//Need Better Name
|
||||
// {
|
||||
// None = 0,
|
||||
// Push = 1,
|
||||
// Pull = 2,
|
||||
// PushPull = Push | Pull
|
||||
// }
|
||||
public interface IBeltMovement
|
||||
{
|
||||
void AdvanceBelt(ItemConveyor conveyor, float delta);
|
||||
void AdvanceBelt(IMovementConveyor conveyor, float delta);
|
||||
// event ItemMoved? OnItemMoved;
|
||||
// delegate void ItemMoved(
|
||||
// IBeltItem item,
|
||||
// ReadOnlySpan<ItemMovementSegment> segments,
|
||||
// float deltaTime
|
||||
// );
|
||||
|
||||
}
|
||||
public sealed class IndividualMovement : IBeltMovement
|
||||
{
|
||||
public void AdvanceBelt(ItemConveyor conveyor, float delta)
|
||||
{
|
||||
if (!conveyor.AnyItems())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var beltDirection = conveyor.GetBeltDirection();
|
||||
if (beltDirection == BeltDirection.NotMoving)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var towardStart = beltDirection == BeltDirection.TowardStart;
|
||||
var next = conveyor.GetConveyorPortFacing(conveyor.GetOutputPort());
|
||||
if (towardStart)
|
||||
{
|
||||
for (int i = 0; i < conveyor.ItemsCount; i++)
|
||||
{
|
||||
var possibleItemTravel = conveyor.GetAvailableTravelForFrontItem(next.HasValue);
|
||||
var maxSpeed = Mathf.Min(conveyor.SpeedMagnitude * delta, possibleItemTravel.Value) * Mathf.Sign(conveyor.SignedSpeed);//Or possibly (int)BeltDirection, givn that they stgore the sign as part of the enum
|
||||
if (maxSpeed == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var endT = Mathf.Clamp(conveyor._items[i].BeltT + maxSpeed, 0, conveyor.Length);
|
||||
|
||||
conveyor._items[i] = conveyor._items[i] with { BeltT = endT };
|
||||
if (endT <= 0)
|
||||
{
|
||||
// segments.Add(new(next.Value.Conveyor, ));
|
||||
if (next.Value.Port.TryInsertItem(conveyor._items[i].Item, LaneSpan.One))
|
||||
{
|
||||
GD.Print("Item Moved");
|
||||
conveyor._items.Remove(conveyor._items[i]);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = conveyor.ItemsCount-1; i >=0 ; i--)
|
||||
{
|
||||
var possibleItemTravel = conveyor.GetAvailableTravelForFrontItem(next.HasValue);
|
||||
var maxSpeed = Mathf.Min(conveyor.SpeedMagnitude * delta, possibleItemTravel.Value) * Mathf.Sign(conveyor.SignedSpeed);//Or possibly (int)BeltDirection, givn that they stgore the sign as part of the enum
|
||||
if (maxSpeed == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var endT = Mathf.Clamp(conveyor._items[i].BeltT + maxSpeed, 0, conveyor.Length);
|
||||
|
||||
conveyor._items[i] = conveyor._items[i] with { BeltT = endT };
|
||||
if (endT >= conveyor.Length)
|
||||
{
|
||||
// segments.Add(new(next.Value.Conveyor, ));
|
||||
if (next.Value.Port.TryInsertItem(conveyor._items[i].Item, LaneSpan.One))
|
||||
{
|
||||
GD.Print("Item Moved");
|
||||
conveyor._items.Remove(conveyor._items[i]);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AdvanceBelt(IMovementConveyor conveyor, float delta)
|
||||
{
|
||||
// GD.Print(conveyor.Items);
|
||||
if (conveyor.GetBeltDirection() == BeltDirection.NotMoving)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// GD.Print("hello");
|
||||
var towardStart = conveyor.GetBeltDirection() == BeltDirection.TowardStart;
|
||||
foreach (var itemRef in towardStart ? conveyor.EnumerateTowardStart() : conveyor.EnumerateTowardEnd())
|
||||
{
|
||||
float maxMove = conveyor.SpeedMagnitude * delta;
|
||||
var space = conveyor.GetDistanceToNextItem(conveyor.GetBeltDirection(),itemRef.Value.BeltT,maxMove,itemRef.Value.LaneSpan);
|
||||
|
||||
|
||||
// space -= ITEMSIZE;//AcountForSPacing
|
||||
// if ()
|
||||
// {
|
||||
// continue;
|
||||
// }
|
||||
|
||||
if (space is ItemBeltObstacle itemOb && itemOb.Distance <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
float amountToMove = Mathf.Min(space.Distance, maxMove);
|
||||
|
||||
itemRef.Replace(itemRef.Value with { BeltT = itemRef.Value.BeltT + amountToMove * Mathf.Sign(conveyor.SignedSpeed)});
|
||||
// GD.PrintS(conveyor.Items.Count,space,amountToMove," "+ itemRef.BeltT,itemRef.Item,conveyor.GetBeltDirection(),towardStart);
|
||||
if (towardStart ? itemRef.Value.BeltT <= 0 : itemRef.Value.BeltT >= conveyor.Length)
|
||||
{
|
||||
var facing = conveyor.GetPortFacing(towardStart ? conveyor.StartPort : conveyor.EndPort);
|
||||
// GD.Print(facing.HasValue(out var slot2),slot2);// , slot2.CanAccept(new TestItem(),LaneSpan.One,0));
|
||||
if (facing.HasValue(out var slot) && slot.TryInsert(itemRef.Value.Item, space is PortBeltObstacle portO ? portO.LaneSpan : itemRef.Value.LaneSpan, 0))
|
||||
{
|
||||
// GD.Print(space is PortBeltObstacle port?port.LaneSpan:itemRef.Value.LaneSpan);
|
||||
// GD.PrintS(itemRef.Value.LaneSpan,itemRef.Value.LaneSpan);
|
||||
// GD.Print(itemRef.Item);
|
||||
itemRef.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public sealed class StrictMovement : IBeltMovement
|
||||
{
|
||||
// public event IBeltMovement.ItemMoved? OnItemMoved;
|
||||
|
||||
public void AdvanceBelt(ItemConveyor conveyor, float delta)
|
||||
{
|
||||
if (!conveyor.AnyItems())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var beltDirection = conveyor.GetBeltDirection();
|
||||
if (beltDirection == BeltDirection.NotMoving)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var towardStart = beltDirection == BeltDirection.TowardStart;
|
||||
// GD.Print("Thing: " + Speed + " "+ conveyor.GetConveyorPortFacing(conveyor.GetOutputPort()));
|
||||
// var moveItemsIntoNextBelt = false;
|
||||
var next = conveyor.GetConveyorPortFacing(conveyor.GetOutputPort());
|
||||
var possibleItemTravel = conveyor.GetAvailableTravelForFrontItem(next.HasValue);
|
||||
|
||||
// return;
|
||||
if (!possibleItemTravel.HasValue)
|
||||
{
|
||||
//There is no items, this should not be called
|
||||
throw new NotSupportedException("This should not e possible if there is no items or the belt is not moving, it should have returend before");
|
||||
}
|
||||
// if (next.HasValue && next.Value.Port.BeltT is BeltTEnd beltTEnd && (next.Value.Port.Direction == PortAccess.In || next.Value.Port.Direction == PortAccess.BiDirectional))
|
||||
// {
|
||||
// possibleItemTravel += next.Value.Conveyor.DistanceFromEnd(beltTEnd.End);
|
||||
// moveItemsIntoNextBelt = true;
|
||||
// }
|
||||
|
||||
|
||||
// var itemPair = conveyor.GetFirstItem();
|
||||
// var distToEnd = conveyor.GetAvailableTravel(conveyor.ItemsCount - 1, beltDirection);//(Speed < 0 ? ConveyorEnd.Start : ConveyorEnd.End);
|
||||
var maxSpeed = Mathf.Min(conveyor.SpeedMagnitude * delta, possibleItemTravel.Value) * Mathf.Sign(conveyor.SignedSpeed);//Or possibly (int)BeltDirection, givn that they stgore the sign as part of the enum
|
||||
if (maxSpeed == 0)
|
||||
{
|
||||
// var fI = conveyor.GetAvailableTravelForFrontItem();
|
||||
// if (fI.HasValue && fI.Value <= 0 && next.Value.Port.TryInsertItem(conveyor.GetFirstItem().Value.Item, LaneSpan.One))
|
||||
// {
|
||||
// GD.Print("Item Moved");
|
||||
// conveyor._items.Remove(conveyor.GetFirstItem().Value);
|
||||
// }
|
||||
|
||||
return;
|
||||
}
|
||||
List<ConveyorSlice> toMove = [];
|
||||
// Dictionary<IBeltItem, List<ItemMovementSegment>> moves = [];
|
||||
for (int i = 0; i < conveyor.ItemsCount; i++)
|
||||
{
|
||||
// List<ItemMovementSegment> segments = [];
|
||||
// var startT = conveyor._items[i].BeltT;
|
||||
var endT = Mathf.Clamp(conveyor._items[i].BeltT + maxSpeed, 0, conveyor.Length);
|
||||
|
||||
conveyor._items[i] = conveyor._items[i] with { BeltT = endT };
|
||||
// segments.Add(new(conveyor, startT, towardStart? Mathf.Max(endT, 0) : Mathf.Min(endT, conveyor.Length),Mathf.Abs(maxSpeed)));
|
||||
if (towardStart ? (endT <= 0) : endT >= conveyor.Length)
|
||||
{
|
||||
toMove.Add(conveyor._items[i]);
|
||||
// segments.Add(new(next.Value.Conveyor, ));
|
||||
}
|
||||
// moves.Add(conveyor._items[i].Item, segments);
|
||||
|
||||
GD.Print($"Item: {conveyor._items[i].Item}, Position:{endT}");
|
||||
}
|
||||
// conveyor._items.ForEach((item) => item = item = Mathf.Clamp(item.Position + maxSpeed, 0, Length));
|
||||
// var list = _items.Where(item=> Speed>=0?item.BeltT >= Length:item.BeltT <= 0).ToList();
|
||||
// GD.Print(list.Count);
|
||||
if (next.HasValue)
|
||||
{
|
||||
foreach (var item in toMove)
|
||||
{
|
||||
GD.Print($"Item: {item.Item}, Position:{item.BeltT} -----------------");
|
||||
//TEST
|
||||
// var v = GetConveyorPortFacing(GetOutputPort());
|
||||
// if (v.HasValue)//Should be true if items are > Length
|
||||
// {
|
||||
// GD.PrintS(v.Value.Port.LocalOffset,v.Value.Port.BeltT,v.Value.Port.Direction,v.Value.Port.PullPush,v.Value.Port.Face);
|
||||
// var end = (next.Value.Port.BeltT as BeltTEnd).End;
|
||||
// next.Value.Conveyor.InsertAtEndWithoutCheck(end, item.Item);
|
||||
if (next.Value.Port.TryInsertItem(item.Item, item.LaneSpan))
|
||||
{
|
||||
GD.Print("Item Moved");
|
||||
conveyor._items.Remove(item);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void AdvanceBelt(IMovementConveyor conveyor, float delta) => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public struct ConveyorSlice(IBeltItem item, float beltT = 0)
|
||||
{
|
||||
public IBeltItem Item = item;
|
||||
public float BeltT = beltT;
|
||||
public LaneSpan LaneSpan = LaneSpan.One;
|
||||
public override string ToString() => $"{Item}, {BeltT}, {LaneSpan}";
|
||||
|
||||
}
|
||||
public readonly struct LaneSpan : IEquatable<LaneSpan>
|
||||
{
|
||||
public readonly ushort Start;
|
||||
public readonly ushort End;
|
||||
public readonly int Width => End - Start;
|
||||
public static readonly LaneSpan One = new(0, 1);
|
||||
public static readonly LaneSpan Zero = new(0, 0);
|
||||
public LaneSpan(ushort start, ushort end)
|
||||
{
|
||||
Start = start;
|
||||
End = end;
|
||||
}
|
||||
// [MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public static bool OverLaps(LaneSpan a, LaneSpan b) => a.Start < b.End && b.Start < a.End;
|
||||
// [MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
public static LaneSpan Shifted(LaneSpan a, int amount) => checked(new LaneSpan((ushort)(a.Start + amount), (ushort)(a.End + amount)));
|
||||
public static bool Encapsulates(LaneSpan a, LaneSpan b) => b.Start>=a.Start && b.End<= b.End;
|
||||
public override string ToString() => $"(Start:{Start}, End:{End})";
|
||||
public bool Equals(LaneSpan other) => Start == other.Start && End == other.End;
|
||||
public static bool operator ==(LaneSpan a, LaneSpan b) => a.Equals(b);
|
||||
public static bool operator !=(LaneSpan a, LaneSpan b) => !a.Equals(b);
|
||||
}
|
||||
public static class Extersions
|
||||
{
|
||||
public static ItemConveyor.BeltDirection DirectionTo(this ItemConveyor.ConveyorEnd end) => end switch
|
||||
{
|
||||
ItemConveyor.ConveyorEnd.Start => ItemConveyor.BeltDirection.TowardStart,
|
||||
ItemConveyor.ConveyorEnd.End => ItemConveyor.BeltDirection.TowardEnd,
|
||||
_ => throw new NotImplementedException(),
|
||||
};
|
||||
}
|
||||
1
src/VoxelGrid/ItemConveyor.cs.uid
Normal file
1
src/VoxelGrid/ItemConveyor.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://cx35jfqkjnou8
|
||||
131
src/VoxelGrid/SlotComponentNode.cs
Normal file
131
src/VoxelGrid/SlotComponentNode.cs
Normal file
@@ -0,0 +1,131 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Chickensoft.AutoInject;
|
||||
using Chickensoft.Introspection;
|
||||
using Godot;
|
||||
|
||||
[Meta(typeof(IAutoNode))]
|
||||
public partial class SlotComponentNode
|
||||
: Node3D , IEquipmentComponent
|
||||
{
|
||||
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
[Export] public SlotDirection Direction;
|
||||
[Export] public Vector3I LocalCellOffset;
|
||||
|
||||
[Dependency] protected IVoxelGridQuery<LayeredEquipment> Grid => this.DependOn<IVoxelGridQuery<LayeredEquipment>>();
|
||||
|
||||
[Dependency] protected IEquipmentContext EquipmentContext =>this.DependOn<IEquipmentContext>();
|
||||
[Dependency] protected IEquipmentComponentRegistry ComponentRegistry => this.DependOn<IEquipmentComponentRegistry>();
|
||||
|
||||
public EquipmentId EquipmentId => EquipmentContext.GetEquipmentId();
|
||||
|
||||
// SO instead of using an hidden list of compeonts on the equimpent, have the equpment provide an reggistery that compnents depend on to register themselves to, and allow for muitple of the same type.
|
||||
public void OnResolved()
|
||||
{
|
||||
GD.PrintS(EquipmentContext, Grid);
|
||||
EquipmentContext.GetEquipment().Tick += Tick;
|
||||
ComponentRegistry.Register(this);
|
||||
GD.Print("Added Tick Listener");
|
||||
}
|
||||
private void Tick()
|
||||
{
|
||||
Grid.GetVoxel(EquipmentContext.GetEquipment().GridPos + LocalCellOffset/*TODO Acount for Direction*/).IfAny(e =>
|
||||
{
|
||||
var slots = e.SelectMany(ee => ee.GetChildren().OfType<SlotComponentNode>().Where(_=> true/*Where slot faces this one and is not output*/)).ToList();
|
||||
// slots.First().AcceptItem(new TestItem());//For testing accuming adding item, should probly sort and cascade so progatrion stops when out of items, but should only be one result, as each componet is one face
|
||||
});
|
||||
// GD.Print("Tryied moveing items test");
|
||||
}
|
||||
public bool AcceptItem(IBeltItem itemStack)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
public override void _ExitTree()
|
||||
{
|
||||
base._ExitTree();
|
||||
EquipmentContext.GetEquipment().Tick -= Tick;
|
||||
ComponentRegistry.Unregister(this);
|
||||
}
|
||||
|
||||
// public Vector3I GetSlotCell() {
|
||||
// return Equipment.GetOccupiedCell() + LocalCellOffset;
|
||||
// }
|
||||
|
||||
// public Vector3 GetSlotWorldPosition() {
|
||||
// return Grid.CellToWorld(GetSlotCell());
|
||||
// }
|
||||
}
|
||||
public interface IEquipmentComponentRegistry {
|
||||
void Register<T>(T component)
|
||||
where T : class, IEquipmentComponent;
|
||||
|
||||
void Unregister<T>(T component)
|
||||
where T : class, IEquipmentComponent;
|
||||
|
||||
T? Get<T>(EquipmentId id)
|
||||
where T : class, IEquipmentComponent;
|
||||
|
||||
IReadOnlyList<T> GetAll<T>(EquipmentId id)
|
||||
where T : class, IEquipmentComponent;
|
||||
}public sealed class EquipmentComponentRegistry
|
||||
: IEquipmentComponentRegistry {
|
||||
|
||||
private readonly Dictionary<
|
||||
EquipmentId,
|
||||
Dictionary<Type, List<object>>
|
||||
> _map = new();
|
||||
|
||||
public void Register<T>(T component)
|
||||
where T : class, IEquipmentComponent {
|
||||
GD.Print(component);
|
||||
if (!_map.TryGetValue(component.EquipmentId, out var types)) {
|
||||
types = new();
|
||||
_map[component.EquipmentId] = types;
|
||||
}
|
||||
|
||||
var type = typeof(T);
|
||||
|
||||
if (!types.TryGetValue(type, out var list)) {
|
||||
list = new();
|
||||
types[type] = list;
|
||||
}
|
||||
|
||||
list.Add(component);
|
||||
}
|
||||
|
||||
public T? Get<T>(EquipmentId id)
|
||||
where T : class, IEquipmentComponent {
|
||||
|
||||
if (_map.TryGetValue(id, out var types) &&
|
||||
types.TryGetValue(typeof(T), out var list))
|
||||
return list[0] as T;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public IReadOnlyList<T> GetAll<T>(EquipmentId id)
|
||||
where T : class, IEquipmentComponent {
|
||||
|
||||
if (_map.TryGetValue(id, out var types) &&
|
||||
types.TryGetValue(typeof(T), out var list))
|
||||
return list.Cast<T>().ToList();
|
||||
|
||||
return Array.Empty<T>();
|
||||
}
|
||||
|
||||
public void Unregister<T>(T component)
|
||||
where T : class, IEquipmentComponent {
|
||||
|
||||
if (_map.TryGetValue(component.EquipmentId, out var types) &&
|
||||
types.TryGetValue(typeof(T), out var list))
|
||||
list.Remove(component);
|
||||
}
|
||||
}
|
||||
|
||||
public interface IEquipmentComponent {
|
||||
EquipmentId EquipmentId { get; }
|
||||
}
|
||||
1
src/VoxelGrid/SlotComponentNode.cs.uid
Normal file
1
src/VoxelGrid/SlotComponentNode.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://drsqsmj0bn1ob
|
||||
250
src/VoxelGrid/SlotFace.cs
Normal file
250
src/VoxelGrid/SlotFace.cs
Normal file
@@ -0,0 +1,250 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using Chickensoft.GodotNodeInterfaces;
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.AutoInject;
|
||||
using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using ChickenGameTest;
|
||||
|
||||
public interface ISlotFace : IVoxelNode // INode3D,
|
||||
{
|
||||
ISlotFace GetConnectingFace();
|
||||
|
||||
}
|
||||
[Tool]
|
||||
[Meta(typeof(IAutoNode))]
|
||||
public partial class SlotFace : Node3D, ISlotFace
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public IEnumerable<Vector3I> Shape => [Vector3I.Zero];
|
||||
|
||||
[Export] public Vector3I VoxelPosition { get; set; }
|
||||
[Export] public Direction VoxelRotation { get; set; }
|
||||
[Export] public bool ShowDebug { get; set; } = false;
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
|
||||
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>();
|
||||
public void OnResolved()
|
||||
{
|
||||
GridRegistry.Register(this);
|
||||
GD.Print("Regestered");
|
||||
}
|
||||
public ISlotFace GetConnectingFace()//TODO there could be tecnaly muiple slots shareing a face, and should be acounted for
|
||||
{
|
||||
var nodes = GridRegistry.Get<ISlotFace>(VoxelPosition + VoxelRotation.ToVector());//TODO Acoount for rotation to get slot direction
|
||||
// GD.Print(nodes.Count());
|
||||
foreach (var item in nodes)
|
||||
{
|
||||
if (item.VoxelRotation.Reverse() == VoxelRotation)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
private static Vector3[] _Square = [new(-.5f, .5f, -.5f), new(.5f, .5f, -.5f), new(.5f, -.5f, -.5f), new(-.5f, -.5f, -.5f), new(-.5f, .5f, -.5f)];
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (ShowDebug)
|
||||
{
|
||||
ShowDebugFace();
|
||||
}
|
||||
}
|
||||
private void ShowDebugFace()
|
||||
{
|
||||
|
||||
var color = Colors.Red;
|
||||
var pos = ToGlobal(new Vector3(0, 0, -.5f));
|
||||
if (!Engine.IsEditorHint())
|
||||
{
|
||||
color = GetConnectingFace() is null ? Colors.Red : Colors.Green;
|
||||
}
|
||||
// var text = $"SlotFace{Name}";
|
||||
// GD.PrintS(color);
|
||||
DebugDraw3D.DrawLinePath([.. _Square.Select(ToGlobal)],color);
|
||||
// DebugDraw3D.DrawText(pos, text, 16);
|
||||
}
|
||||
}
|
||||
public interface IVoxelGridRegistry
|
||||
{
|
||||
[Obsolete]
|
||||
void Register<T>(T voxelNode) where T : IVoxelNode;
|
||||
void Register<T>(T voxelNode, params Vector3I[] positions);
|
||||
void UnRegister<T>(T voxelNode) where T : IVoxelNode=> UnRegister(voxelNode.Id);
|
||||
void UnRegister<T>(T voxelNode, params Vector3I[] positions);
|
||||
void UnRegister(Guid id);
|
||||
IEnumerable<T> Get<T>(Vector3I voxelPos);
|
||||
IEnumerable<T> Get<T>(params Vector3I[] voxelPos);
|
||||
|
||||
}
|
||||
public sealed class VoxelRegistry : IVoxelGridRegistry
|
||||
{
|
||||
private Dictionary<Vector3I, ICollection<object>> _data;
|
||||
public VoxelRegistry()
|
||||
{
|
||||
_data = new();
|
||||
}
|
||||
public IEnumerable<T> Get<T>(Vector3I voxelPos)
|
||||
{
|
||||
if (!_data.TryGetValue(voxelPos, out var entrys))
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
foreach (var item in entrys.OfType<T>())
|
||||
{
|
||||
yield return item;
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<T> Get<T>(params Vector3I[] voxelPos)
|
||||
{
|
||||
foreach (var item in voxelPos)
|
||||
{
|
||||
foreach (var item2 in Get<T>(item))
|
||||
{
|
||||
yield return item2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Register<T>(T voxelNode) where T : IVoxelNode
|
||||
{
|
||||
void register(Vector3I pos, T point)
|
||||
{
|
||||
if (!_data.TryGetValue(pos, out var entrys))
|
||||
{
|
||||
entrys = [];
|
||||
_data[pos] = entrys;
|
||||
}
|
||||
entrys.Add(point);
|
||||
}
|
||||
voxelNode.Shape.ToList().ForEach(
|
||||
item =>
|
||||
{
|
||||
register(item + voxelNode.VoxelPosition, voxelNode);//TODO Acount For Rotation
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public void Register<T>(T voxelNode, params Vector3I[] positions)
|
||||
{
|
||||
void register(Vector3I pos, T point)
|
||||
{
|
||||
if (!_data.TryGetValue(pos, out var entrys))
|
||||
{
|
||||
entrys = [];
|
||||
_data[pos] = entrys;
|
||||
}
|
||||
entrys.Add(point);
|
||||
}
|
||||
foreach (var pos in positions)
|
||||
{
|
||||
if (positions.Length>1){
|
||||
GD.Print("hhhhhhhhh ",pos);}
|
||||
register(pos, voxelNode);//TODO Acount For Rotation
|
||||
}
|
||||
}
|
||||
public void UnRegister(Guid id) => throw new NotImplementedException();
|
||||
public void UnRegister<T>(T voxelNode, params Vector3I[] positions)
|
||||
{
|
||||
void unRegister(Vector3I pos, T point)
|
||||
{
|
||||
if (!_data.TryGetValue(pos, out var entrys))
|
||||
{
|
||||
return;
|
||||
}
|
||||
entrys.Remove(point);
|
||||
}
|
||||
foreach (var item in positions)
|
||||
{
|
||||
unRegister(item, voxelNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
public interface IVoxelNode
|
||||
{
|
||||
Guid Id { get; }
|
||||
Vector3I VoxelPosition { get; }
|
||||
Direction VoxelRotation { get; } //TODO replace with struct/record for static rotation typeing
|
||||
IEnumerable<Vector3I> Shape { get; }//TOPO make SHape able to account for occpancy/ partial filled voxels.
|
||||
}
|
||||
|
||||
public enum Direction
|
||||
{
|
||||
Up, Down, Left, Right, Front, Back
|
||||
}
|
||||
public static class DirectionExtession
|
||||
{
|
||||
public static Vector3I ToVector(this Direction direction) => direction switch
|
||||
{
|
||||
Direction.Up => Vector3I.Up,
|
||||
Direction.Down => Vector3I.Down,
|
||||
Direction.Left => Vector3I.Left,
|
||||
Direction.Right => Vector3I.Right,
|
||||
Direction.Front => Vector3I.Forward,
|
||||
Direction.Back => Vector3I.Back,
|
||||
_ => throw new NotSupportedException($"{nameof(direction)} does not support value {direction}")
|
||||
};
|
||||
public static Direction Reverse(this Direction direction) => direction switch
|
||||
{
|
||||
Direction.Up => Direction.Down,
|
||||
Direction.Down => Direction.Up,
|
||||
Direction.Left => Direction.Right,
|
||||
Direction.Right => Direction.Left,
|
||||
Direction.Front => Direction.Back,
|
||||
Direction.Back => Direction.Front,
|
||||
_ => throw new NotSupportedException($"{nameof(direction)} does not support value {direction}")
|
||||
};
|
||||
public static Direction RotateClockWise(this Direction direction) => direction switch
|
||||
{
|
||||
Direction.Up => Direction.Up,
|
||||
Direction.Down => Direction.Down,
|
||||
Direction.Left => Direction.Back,
|
||||
Direction.Right => Direction.Front,
|
||||
Direction.Front => Direction.Right,
|
||||
Direction.Back => Direction.Left,
|
||||
_ => throw new NotSupportedException($"{nameof(direction)} does not support value {direction}")
|
||||
};
|
||||
public static Direction RotateCounterClockWise(this Direction direction) => direction switch
|
||||
{
|
||||
Direction.Up => Direction.Up,
|
||||
Direction.Down => Direction.Down,
|
||||
Direction.Right => Direction.Front,
|
||||
Direction.Left => Direction.Back,
|
||||
Direction.Back => Direction.Right,
|
||||
Direction.Front => Direction.Left,
|
||||
_ => throw new NotSupportedException($"{nameof(direction)} does not support value {direction}")
|
||||
};
|
||||
}
|
||||
public interface IStorage<T> where T : class
|
||||
{
|
||||
StorageDefinition Definition { get; }
|
||||
IEnumerable<T> GetAllItems();
|
||||
void Remove(T item);
|
||||
bool TryInsert(T item);
|
||||
|
||||
record StorageDefinition(string Name, ItemType Type);
|
||||
}
|
||||
public record ItemType(string Name);
|
||||
public partial class ItemStoragePool : Node, IStorage<IBeltItem>
|
||||
{
|
||||
[Export] public int MaxAmount { get; set; } = 5;
|
||||
public IStorage<IBeltItem>.StorageDefinition Definition { get; set; } = default!;
|
||||
private readonly List<IBeltItem> _items = [];
|
||||
public IEnumerable<IBeltItem> GetAllItems() => _items;
|
||||
public bool TryInsert(IBeltItem item)
|
||||
{
|
||||
if (_items.Count >= MaxAmount)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_items.Add(item);
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Remove(IBeltItem item) => _items.Remove(item);
|
||||
}
|
||||
1
src/VoxelGrid/SlotFace.cs.uid
Normal file
1
src/VoxelGrid/SlotFace.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dao8u7hn0yadp
|
||||
711
src/VoxelGrid/TestItemConveyor.cs
Normal file
711
src/VoxelGrid/TestItemConveyor.cs
Normal file
@@ -0,0 +1,711 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using Chickensoft.Introspection;
|
||||
using Chickensoft.AutoInject;
|
||||
using Godot;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Chickensoft.Sync.Primitives;
|
||||
using SJK.Functional;
|
||||
|
||||
public class Sorted1DList<T>
|
||||
{
|
||||
private readonly AutoList<T> _items = [];
|
||||
#if DEBUG
|
||||
private readonly AutoList<T>.Binding _binding;
|
||||
#endif
|
||||
private readonly Func<T, float> _getPosition;
|
||||
public Sorted1DList(Func<T, float> getPos)
|
||||
{
|
||||
_getPosition = getPos;
|
||||
// return;
|
||||
#if DEBUG
|
||||
_binding = _items.Bind();
|
||||
_binding.OnUpdate((a, b, c) =>
|
||||
{
|
||||
// GD.PrintS("++++++++++++++++",a,b,c);
|
||||
});
|
||||
// return;
|
||||
_binding.OnModify(() =>
|
||||
{
|
||||
for (int i = 1; i < _items.Count; i++)
|
||||
{
|
||||
if (_getPosition(_items[i - 1]) >= _getPosition(_items[i]))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"AutoList not sorted at index {i - 1} → {i}. ({_getPosition(_items[i-1])},{_getPosition(_items[i])})");
|
||||
}
|
||||
}
|
||||
});
|
||||
#endif
|
||||
|
||||
}
|
||||
public int Count => _items.Count;
|
||||
public IAutoList<T> Items => _items;
|
||||
public IEnumerable<ItemHandle> EnumerateTowardEnd(int? startIndex = null)
|
||||
{
|
||||
if (startIndex.HasValue && startIndex >= Count)
|
||||
{
|
||||
throw new IndexOutOfRangeException($"{nameof(startIndex)}:{startIndex} can not be greater then or equal to {nameof(Count)}:{Count}");
|
||||
}
|
||||
for (int i = Mathf.Max(0,startIndex ?? 0); i < _items.Count; i++)
|
||||
{
|
||||
bool removed = false;
|
||||
yield return new(_items[i],
|
||||
() =>
|
||||
{
|
||||
if (removed)
|
||||
{
|
||||
throw new NotSupportedException($"{nameof(ItemHandle.Remove)} can only be called once per item.");
|
||||
}
|
||||
removed = true;
|
||||
_items.RemoveAt(i);
|
||||
i--;
|
||||
},
|
||||
(replaced) =>
|
||||
{
|
||||
if (removed)
|
||||
{
|
||||
throw new NotSupportedException("Can not Replace an Item after Removing it");
|
||||
}
|
||||
_items[i] = replaced;
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
public IEnumerable<ItemHandle> EnumerateTowardStart(int? startIndex = null)
|
||||
{
|
||||
if (startIndex.HasValue && startIndex < 0)
|
||||
{
|
||||
throw new IndexOutOfRangeException($"{nameof(startIndex)}:{startIndex} can not be less than zero");
|
||||
}
|
||||
for (int i = Math.Min(startIndex ?? (_items.Count - 1), _items.Count -1); i>=0 ; i--)
|
||||
{
|
||||
bool removed = false;
|
||||
yield return new(_items[i],
|
||||
() =>
|
||||
{
|
||||
if (removed)
|
||||
{
|
||||
throw new NotSupportedException($"{nameof(ItemHandle.Remove)} can only be called once per item.");
|
||||
}
|
||||
removed = true;
|
||||
_items.RemoveAt(i);
|
||||
// i--;
|
||||
},
|
||||
(replaced) =>
|
||||
{
|
||||
if (removed)
|
||||
{
|
||||
throw new NotSupportedException("Can not Replace an Item after Removing it");
|
||||
}
|
||||
|
||||
if (i-1>=0 && _getPosition(_items[i-1]) >= _getPosition(replaced))
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
if (i+1<Count && _getPosition(_items[i+1]) <= _getPosition(replaced))
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
_items[i] = replaced;
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
public void Insert(T item)
|
||||
{
|
||||
float pos = _getPosition(item);
|
||||
if (_items.Count == 0 || pos >= _getPosition(_items[^1]))
|
||||
{
|
||||
_items.Add(item);
|
||||
return;
|
||||
}
|
||||
if (pos <=_getPosition(_items[0]))
|
||||
{
|
||||
_items.Insert(0, item);
|
||||
return;
|
||||
}
|
||||
var index = LowerBound(pos);
|
||||
_items.Insert(index, item);
|
||||
}
|
||||
private int LowerBound(float pos)
|
||||
{
|
||||
int lo = 0;
|
||||
int hi = _items.Count;
|
||||
|
||||
while (lo < hi)
|
||||
{
|
||||
int mid = (lo + hi) >> 1;
|
||||
|
||||
if (_getPosition(_items[mid]) < pos)
|
||||
{
|
||||
lo = mid + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
hi = mid;
|
||||
}
|
||||
}
|
||||
|
||||
return lo;
|
||||
}
|
||||
public (Option<T> lower, Option<T> upper) GetNeighbors(float pos)
|
||||
{
|
||||
int index = LowerBound(pos);
|
||||
|
||||
var lower = index > 0 ? Option<T>.Some(_items[index - 1]) : Option<T>.None;
|
||||
var upper = index < _items.Count ? Option<T>.Some(_items[index]) : Option<T>.None;
|
||||
|
||||
return (lower, upper);
|
||||
}
|
||||
public readonly struct ItemHandle
|
||||
{
|
||||
private readonly Action _remove;
|
||||
private readonly Action<T> _replace;
|
||||
public T Value { get; }
|
||||
public ItemHandle(T value, Action remove, Action<T> replace)
|
||||
{
|
||||
Value = value;
|
||||
_remove = remove;
|
||||
_replace = replace;
|
||||
}
|
||||
public void Remove() => _remove();
|
||||
public void Replace(T slice) => _replace(slice);
|
||||
}
|
||||
}
|
||||
[Meta(typeof(IAutoNode))][Tool]
|
||||
public partial class TestItemConveyor : Node, IMovementConveyor
|
||||
{
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
[Dependency] public ItemConveyor.IBeltMovement MovementSystem => this.DependOn<ItemConveyor.IBeltMovement>(() => new ItemConveyor.IndividualMovement());
|
||||
[Dependency] public IVoxelGridRegistry GridRegistry => this.DependOn<IVoxelGridRegistry>();
|
||||
// private readonly AutoList<ConveyorSlice> _items = [];
|
||||
// public IAutoList<ConveyorSlice> Items => _items;
|
||||
public readonly Sorted1DList<ConveyorSlice> Items = new(pos => pos.BeltT);
|
||||
// private AutoList<ConveyorSlice>.Binding _itemsBinding;
|
||||
public IBeltPort StartPort { get; set; }
|
||||
// public IBeltSlotProfile StartPort => new ItemConveyor.ConveyorPort() {
|
||||
// Face = Direction.Back,
|
||||
// MovementConveyor = this,
|
||||
// LocalOffset = Position,
|
||||
// BeltT = new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.Start),
|
||||
// PullPush = TransferMode.PushPull ,
|
||||
// Direction = PortAccess.BiDirectional,
|
||||
// AcceptItemFunc = (item,offset)=>{ _items.Insert(0,new ConveyorSlice(item,offset)); return true;},
|
||||
// CanAcceptItemFunc = (item, offset) =>
|
||||
// {
|
||||
// return .25f <= GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, 0, 5f, LaneSpan.One);
|
||||
// }
|
||||
// };
|
||||
public IBeltPort EndPort { get; set; }
|
||||
// public IBeltSlotProfile EndPort => new ItemConveyor.ConveyorPort() {
|
||||
// Face = Direction.Front,
|
||||
// MovementConveyor = this,
|
||||
// Direction = PortAccess.BiDirectional,
|
||||
// LocalOffset = Position,
|
||||
// BeltT = new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.End),
|
||||
// PullPush = TransferMode.PushPull,
|
||||
// AcceptItemFunc = (item,offset)=>{ _items.Add(new ConveyorSlice(item,Length-offset)); return true;},
|
||||
// CanAcceptItemFunc = (item,offset)=> .25f >= GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart,Length,.5f,LaneSpan.One)
|
||||
// };
|
||||
public IList<IBeltPort> OtherPorts = [];
|
||||
// [Export] public Vector3I Position { get; set; } = default!;
|
||||
//Speed per unit time
|
||||
private readonly AutoValue<float> _speed = new(1);
|
||||
public IAutoValue<float> SpeedValue => _speed;
|
||||
|
||||
public float SignedSpeed
|
||||
{
|
||||
get => _speed.Value;
|
||||
set => _speed.Value = value;
|
||||
}
|
||||
|
||||
public float SpeedMagnitude
|
||||
{
|
||||
get => Mathf.Abs(_speed.Value);
|
||||
set => _speed.Value = Mathf.Abs(value) * Mathf.Sign(_speed.Value);
|
||||
}
|
||||
public bool IsReversed { get => _speed.Value < 0; set => _speed.Value = SpeedMagnitude * (value ? -1 : 1); }
|
||||
// public IList<ConveyorSlice> Items => _items;
|
||||
public float Length { get; set; } = 1;
|
||||
|
||||
// public IEnumerable<ConveyorSlice> EnumerateTowardEnd() => _items;
|
||||
|
||||
// public IEnumerable<ConveyorSlice> EnumerateTowardStart() => _items.Reverse();
|
||||
public ItemConveyor.BeltDirection GetBeltDirection() => IsReversed ? ItemConveyor.BeltDirection.TowardStart : ItemConveyor.BeltDirection.TowardEnd;
|
||||
public ItemConveyor.IBeltMovement GetMovementPolicy() => MovementSystem;
|
||||
public IEnumerable<IBeltPort> GetPorts() => [StartPort, EndPort, .. OtherPorts];
|
||||
// public IEnumerable<IBeltSlotProfile> GetPorts() => [StartPort, EndPort,
|
||||
// new ItemConveyor.ConveyorPort(){
|
||||
// BeltT = new ItemConveyor.BeltTOffset(.5f),
|
||||
// Face = Direction.Right,
|
||||
// Direction = PortAccess.InOut,
|
||||
// PullPush = TransferMode.Passive,
|
||||
// MovementConveyor = this,
|
||||
// LocalOffset = Position,
|
||||
// },
|
||||
// new ItemConveyor.ConveyorPort(){
|
||||
// BeltT = new ItemConveyor.BeltTOffset(.5f),
|
||||
// Face = Direction.Left,
|
||||
// Direction = PortAccess.InOut,
|
||||
// PullPush = TransferMode.Passive,
|
||||
// MovementConveyor = this,
|
||||
// LocalOffset = Position,
|
||||
// },
|
||||
// new ItemConveyor.ConveyorPort(){
|
||||
// BeltT = new ItemConveyor.BeltTOffset(.5f),
|
||||
// Face = Direction.Up,
|
||||
// Direction = PortAccess.In,
|
||||
// PullPush = TransferMode.Passive,
|
||||
// MovementConveyor = this,
|
||||
// LocalOffset = Position,
|
||||
// }
|
||||
// ];Path3D.Curve.SampleBakedWithRotation(b.BeltT).Basis.X*b.Item.Width
|
||||
public void OnResolved()
|
||||
{
|
||||
if (Engine.IsEditorHint())
|
||||
{
|
||||
return;
|
||||
}
|
||||
var timer = new Timer() { WaitTime = .05f, Autostart = true };
|
||||
AddChild(timer);
|
||||
timer.Timeout += () => MovementSystem.AdvanceBelt(this, (float)timer.WaitTime);
|
||||
// Items.Add(new ConveyorSlice(new TestItem(), 0));
|
||||
// StartPort = new ConveyorPort(this,
|
||||
// new BeltPortProfile(Position, Direction.Back, 1, PortAccess.BiDirectional),
|
||||
// new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.Start),
|
||||
// (item, offset) =>
|
||||
// {
|
||||
// var obstacle = 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;});
|
||||
// EndPort = new ConveyorPort(this,
|
||||
// new BeltPortProfile(Position, Direction.Front, 1, PortAccess.BiDirectional),
|
||||
// new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.End),
|
||||
// (item, offset) =>
|
||||
// {
|
||||
// var obstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, 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;});
|
||||
if (StartPort is null || EndPort is null)
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
foreach (var item in GetPorts())
|
||||
{
|
||||
var points = item.Points();
|
||||
GridRegistry.Register(item, [.. points]);
|
||||
}
|
||||
StartPort.TryInsert(new TestItem(), LaneSpan.Shifted(LaneSpan.One,0), 0);
|
||||
// StartPort.TryInsert(new TestItem(), LaneSpan.Sh fted(LaneSpan.One,1), 0);
|
||||
// Items.Insert(new ConveyorSlice(new TestItem(), 0));
|
||||
// _items.Add(new (new TestItem()));
|
||||
}
|
||||
public override void _Ready()
|
||||
{
|
||||
// _itemsBinding = _items.Bind();
|
||||
// _itemsBinding.OnModify(() =>
|
||||
// {
|
||||
|
||||
// });
|
||||
// OnResolved();
|
||||
base._Ready();
|
||||
//test
|
||||
|
||||
}
|
||||
//ChatGPT Assisted
|
||||
public BeltObstacle GetDistanceToNextItem(
|
||||
ItemConveyor.BeltDirection beltDirection,
|
||||
float itemBeltT,
|
||||
float maxDistToCheck,
|
||||
LaneSpan laneSpan,
|
||||
HashSet<IMovementConveyor>? visited = null)
|
||||
{
|
||||
if (beltDirection == ItemConveyor.BeltDirection.NotMoving)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
visited ??= [];
|
||||
if (!visited.Add(this))
|
||||
{
|
||||
return new BeltObstacle(0);
|
||||
}
|
||||
|
||||
bool towardStart = beltDirection == ItemConveyor.BeltDirection.TowardStart;
|
||||
|
||||
// 1️⃣ Try to find next item on this conveyor
|
||||
var nextItem = FindNextLocalItem(towardStart, itemBeltT, laneSpan);
|
||||
// GD.Print(nextItem);
|
||||
if (nextItem.HasValue(out var item))
|
||||
{
|
||||
return new ItemBeltObstacle(MathF.Abs(itemBeltT - item.BeltT) - ItemConveyor.ITEMSIZE, item, laneSpan);
|
||||
// return MathF.Abs(itemBeltT - item.BeltT);//TODO UseLaneSpan inadditon
|
||||
}
|
||||
|
||||
// 2️⃣ No local item → try crossing into adjacent conveyor
|
||||
return GetDistanceAcrossPort(
|
||||
towardStart,
|
||||
itemBeltT,
|
||||
maxDistToCheck,
|
||||
laneSpan,
|
||||
visited
|
||||
);
|
||||
}
|
||||
//ChatGPT Assisted
|
||||
private IOption<ConveyorSlice> FindNextLocalItem(
|
||||
bool towardStart,
|
||||
float itemBeltT,
|
||||
LaneSpan laneSpan)
|
||||
{
|
||||
var sequence = towardStart
|
||||
? EnumerateTowardStart()
|
||||
: EnumerateTowardEnd();
|
||||
|
||||
return sequence
|
||||
.Where(i => towardStart ? i.Value.BeltT < itemBeltT : i.Value.BeltT > itemBeltT)
|
||||
.Where(i => LaneSpan.OverLaps(i.Value.LaneSpan, laneSpan))
|
||||
.Select(item => item.Value)
|
||||
.FirstOrNone();
|
||||
}
|
||||
//ChatGPT Assisted
|
||||
private BeltObstacle GetDistanceAcrossPort(
|
||||
bool towardStart,
|
||||
float itemBeltT,
|
||||
float maxDistToCheck,
|
||||
LaneSpan laneSpan,
|
||||
HashSet<IMovementConveyor> visited)
|
||||
{
|
||||
var port = towardStart ? StartPort : EndPort;
|
||||
var boundaryT = towardStart ? 0f : Length;
|
||||
var distanceToBoundary = MathF.Abs(itemBeltT - boundaryT);
|
||||
|
||||
// 🚫 Boundary already exceeds budget
|
||||
// if (distanceToBoundary >= maxDistToCheck)
|
||||
// {
|
||||
// return maxDistToCheck;
|
||||
// }
|
||||
|
||||
|
||||
|
||||
|
||||
var neighborPort = FindFacingConveyorPort(port);
|
||||
if (!neighborPort.HasValue(out var conveyorPort))
|
||||
{
|
||||
var o = GetPortFacing(port);
|
||||
if (o.HasValue(out var otherport))
|
||||
{
|
||||
var mappedLane2 = MapLaneOrFail(port, otherport, laneSpan);
|
||||
if (mappedLane2.HasValue(out var lane2))
|
||||
{
|
||||
return new PortBeltObstacle(distanceToBoundary,lane2,otherport);
|
||||
// return new BeltObstacle(ObstacleKind.Boundary,distanceToBoundary+1,lane1,this,null, null);//TODO THIS SHOULD GET SPACE IN PORT SO ITEMS KNOW IF THEY CAN FIT
|
||||
|
||||
}
|
||||
return new BoundaryBeltObstacle(distanceToBoundary);
|
||||
// return new BeltObstacle(ObstacleKind.Boundary,distanceToBoundary,laneSpan,this,null, null);//TODO THIS SHOULD GET SPACE IN PORT SO ITEMS KNOW IF THEY CAN FIT
|
||||
}
|
||||
return new BoundaryBeltObstacle(distanceToBoundary);
|
||||
}
|
||||
|
||||
if (conveyorPort.BeltT is not ItemConveyor.BeltTEnd end)
|
||||
{
|
||||
return new BoundaryBeltObstacle(distanceToBoundary);
|
||||
}
|
||||
|
||||
var mappedLane = MapLaneOrFail(port, conveyorPort, laneSpan);
|
||||
if (!mappedLane.HasValue(out var lane))
|
||||
{
|
||||
return new BoundaryBeltObstacle(distanceToBoundary);
|
||||
}
|
||||
|
||||
var nextDirection = DirectionAwayFromEnd(end.End);
|
||||
var startT = end.End == ItemConveyor.ConveyorEnd.Start
|
||||
? 0f
|
||||
: conveyorPort.Conveyor.Length;
|
||||
|
||||
// 🔻 Remaining budget after reaching boundary
|
||||
// var remainingDist = maxDistToCheck - distanceToBoundary;
|
||||
// if (remainingDist <= 0f)
|
||||
// {
|
||||
// return maxDistToCheck;
|
||||
// }
|
||||
|
||||
var recursiveDistance =
|
||||
conveyorPort.Conveyor.GetDistanceToNextItem(
|
||||
nextDirection,
|
||||
startT,
|
||||
maxDistToCheck,
|
||||
lane,
|
||||
visited
|
||||
);
|
||||
return recursiveDistance with { Distance = recursiveDistance.Distance + distanceToBoundary };
|
||||
// return new BeltObstacle(recursiveDistance.Kind, recursiveDistance.DistanceToCenter + distanceToBoundary, recursiveDistance.LaneSpan, recursiveDistance.Conveyor, recursiveDistance.Item,null);
|
||||
// return distanceToBoundary + recursiveDistance;
|
||||
}
|
||||
private IOption<ConveyorPort> FindFacingConveyorPort(
|
||||
IBeltPort slot)
|
||||
{
|
||||
var targetpos = slot.Profile.LocalOffset.TransformDirection(Vector3I.Forward);
|
||||
// var targetPos = port.Profile.Position + port.Profile.Face.ToVector();
|
||||
return GridRegistry
|
||||
.Get<IBeltPort>(slot.Profile.LocalOffset.LocalToWorld(Vector3I.Forward))
|
||||
|
||||
.Where(port => port is ConveyorPort)
|
||||
.Where(port => port.Profile.LocalOffset.TransformDirection(Vector3I.Forward) == slot.Profile.LocalOffset.TransformDirection(Vector3I.Back))
|
||||
.FirstOrNone()
|
||||
.Bind(p => p is ConveyorPort cp
|
||||
? cp.ToOption()
|
||||
: None<ConveyorPort>.Of());
|
||||
}
|
||||
public IOption<IBeltPort> GetPortFacing(IBeltPort slot)
|
||||
{
|
||||
var toCheck = new List<Vector3I>();
|
||||
for (int i = 0; i < slot.Profile.Width; i++)
|
||||
{
|
||||
toCheck.Add(slot.Profile.LocalOffset.LocalToWorld(Vector3I.Forward));
|
||||
}
|
||||
return GridRegistry
|
||||
.Get<IBeltPort>([.. toCheck])
|
||||
.Where(port => port.Profile.LocalOffset.TransformDirection(Vector3I.Forward) == slot.Profile.LocalOffset.TransformDirection(Vector3I.Back))
|
||||
.FirstOrNone();
|
||||
}
|
||||
private IOption<LaneSpan> MapLaneOrFail(
|
||||
IBeltPort from,
|
||||
IBeltPort to,
|
||||
LaneSpan incoming)
|
||||
{
|
||||
// return None<LaneSpan>.Of();
|
||||
bool mirror = false;
|
||||
// full mapping of the source port
|
||||
var mapped = from.MapLaneSpanToFacingPort(to);
|
||||
|
||||
if (mapped == LaneSpan.Zero)
|
||||
return LaneSpan.Zero.ToOption();
|
||||
|
||||
int sourceWidth = from.Profile.Width;
|
||||
int targetWidth = mapped.Width;
|
||||
|
||||
int startOffset = incoming.Start; // start relative to source port
|
||||
int spanLength = incoming.Width;
|
||||
|
||||
int newStart;
|
||||
if (mirror)
|
||||
{
|
||||
// mirrored: right side of incoming aligns with right side of mapped
|
||||
newStart = mapped.End - (startOffset + spanLength);
|
||||
}
|
||||
else
|
||||
{
|
||||
// normal: left side of incoming aligns with left side of mapped
|
||||
newStart = mapped.Start + startOffset;
|
||||
}
|
||||
|
||||
int newEnd = newStart + spanLength;
|
||||
|
||||
// clamp to mapped range
|
||||
newStart = Math.Max(mapped.Start, newStart);
|
||||
newEnd = Math.Min(mapped.End, newEnd);
|
||||
|
||||
GD.Print(incoming, new LaneSpan((ushort)newStart, (ushort)newEnd));
|
||||
if (newStart >= newEnd)
|
||||
{
|
||||
GD.Print("none");
|
||||
return None<LaneSpan>.Of();
|
||||
}
|
||||
|
||||
return new LaneSpan((ushort)newStart, (ushort)newEnd).ToOption();
|
||||
}
|
||||
|
||||
private ItemConveyor.BeltDirection DirectionAwayFromEnd(ItemConveyor.ConveyorEnd conveyorEnd) => conveyorEnd switch {ItemConveyor.ConveyorEnd.Start=>ItemConveyor.BeltDirection.TowardEnd,ItemConveyor.ConveyorEnd.End=>ItemConveyor.BeltDirection.TowardStart};
|
||||
public IEnumerable<Sorted1DList<ConveyorSlice>.ItemHandle> EnumerateTowardEnd() => Items.EnumerateTowardEnd();
|
||||
public IEnumerable<Sorted1DList<ConveyorSlice>.ItemHandle> EnumerateTowardStart() => Items.EnumerateTowardStart();
|
||||
private static Vector3[] _Square = [new(-.5f, .5f, -.5f), new(.5f, .5f, -.5f), new(.5f, -.5f, -.5f), new(-.5f, -.5f, -.5f), new(-.5f, .5f, -.5f)];
|
||||
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
if (StartPort is null || EndPort is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
foreach (var item in GetPorts())
|
||||
{
|
||||
for (int ii = 0; ii < item.Profile.Width; ii++)
|
||||
{
|
||||
DebugDraw3D.DrawLinePath(_Square.Select(i => item.Profile.LocalOffset.ToGodot().TranslatedLocal(i).TranslatedLocal(new Vector3(ii,0,0)).Origin).ToArray(),(!Engine.IsEditorHint())&&GetPortFacing(item).HasValue()?Colors.Green:Colors.Red);
|
||||
|
||||
}
|
||||
// DebugDraw3D.DrawArrow(item.Profile.LocalOffset.Origin, item.Profile.LocalOffset.LocalToWorld(item.Profile.Face.ToVector()),(!Engine.IsEditorHint())&&GetPortFacing(item).HasValue()?Colors.Green:Colors.Red);
|
||||
}
|
||||
}
|
||||
|
||||
public ConveyorPort CreatePort(BeltPortProfile profile, ItemConveyor.BeltT beltT, LaneSpan laneSpan)
|
||||
{
|
||||
// var towardStartEnd = beltT is ItemConveyor.BeltTEnd end && end.End == ItemConveyor.ConveyorEnd.End;
|
||||
bool accept(IBeltItem item, float offset, LaneSpan lane)
|
||||
{
|
||||
if (beltT is ItemConveyor.BeltTEnd end)
|
||||
{
|
||||
var startBeltT = end.End == ItemConveyor.ConveyorEnd.Start ? 0 : Length;
|
||||
var obstacle = GetDistanceToNextItem(ItemConveyor.SwapEnd(end.End).DirectionTo(), startBeltT, offset, lane);
|
||||
var distanceAllowed = obstacle.Distance;
|
||||
return ItemConveyor.ITEMSIZE < distanceAllowed;
|
||||
}
|
||||
else if (beltT is ItemConveyor.BeltTOffset endOffset)
|
||||
{
|
||||
return HasClearance(endOffset.T,lane);
|
||||
// var lowerObstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart, endOffset.T, ItemConveyor.ITEMSIZE,LaneSpan.One);
|
||||
// var upperObstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, endOffset.T, ItemConveyor.ITEMSIZE,LaneSpan.One);
|
||||
// var lowerDistanceAllowed = lowerObstacle.DistanceToCenter - (lowerObstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
|
||||
// var upperObstacleAllowed = upperObstacle.DistanceToCenter - (upperObstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
|
||||
// return ItemConveyor.ITEMSIZE < lowerDistanceAllowed && ItemConveyor.ITEMSIZE < upperObstacleAllowed;
|
||||
}
|
||||
// return true;
|
||||
|
||||
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
// if (beltT is ItemConveyor.BeltTEnd end && end.End == ItemConveyor.ConveyorEnd.End){
|
||||
// accept = (IBeltItem item, float offset) =>
|
||||
// {
|
||||
// var obstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardStart, Length, offset, LaneSpan.One);
|
||||
// return ItemConveyor.ITEMSIZE < obstacle.DistanceToCenter - (obstacle.IsItem ? ItemConveyor.ITEMSIZE : 0);
|
||||
// };
|
||||
// }
|
||||
bool tryInsert(IBeltItem item, float offset, LaneSpan laneSpan)
|
||||
{
|
||||
if (beltT is ItemConveyor.BeltTEnd end)
|
||||
{
|
||||
if (end.End == ItemConveyor.ConveyorEnd.End)
|
||||
{
|
||||
Items.Insert(new (item, Length-offset){LaneSpan = laneSpan});
|
||||
// _items.Add(new(item, Length-offset));
|
||||
}
|
||||
else
|
||||
{
|
||||
Items.Insert(new (item, offset) {LaneSpan = laneSpan});
|
||||
// _items.Insert(0, new(item, offset));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (beltT is ItemConveyor.BeltTOffset endOffset)
|
||||
{
|
||||
|
||||
if (!HasClearance(endOffset.T, LaneSpan.One))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Items.Insert(new (item, endOffset.T){LaneSpan = laneSpan});
|
||||
// int insertIndex = FindInsertIndex(endOffset.T);
|
||||
|
||||
// _items.Insert(insertIndex, new(item, endOffset.T));
|
||||
|
||||
return true;
|
||||
}
|
||||
// _items.Insert(0,new ConveyorSlice(item,0));
|
||||
// return true;
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
var port = new ConveyorPort(this,
|
||||
profile,
|
||||
beltT,
|
||||
accept,
|
||||
tryInsert);
|
||||
return port;
|
||||
}
|
||||
private bool HasClearance(float centerT, LaneSpan span)
|
||||
{
|
||||
var lower = GetDistanceToNextItem(
|
||||
ItemConveyor.BeltDirection.TowardStart,
|
||||
centerT,
|
||||
ItemConveyor.ITEMSIZE,
|
||||
span
|
||||
);
|
||||
|
||||
var upper = GetDistanceToNextItem(
|
||||
ItemConveyor.BeltDirection.TowardEnd,
|
||||
centerT,
|
||||
ItemConveyor.ITEMSIZE,
|
||||
span
|
||||
);
|
||||
|
||||
float lowerAllowed =
|
||||
lower.Distance;// -
|
||||
// (lower.IsItem ? ItemConveyor.ITEMSIZE : 0);
|
||||
|
||||
float upperAllowed =
|
||||
upper.Distance;// -
|
||||
// (upper.IsItem ? ItemConveyor.ITEMSIZE : 0);
|
||||
|
||||
return ItemConveyor.ITEMSIZE < lowerAllowed &&
|
||||
ItemConveyor.ITEMSIZE < upperAllowed;
|
||||
}
|
||||
|
||||
}
|
||||
// Items.Add(new ConveyorSlice(new TestItem(), 0));
|
||||
// StartPort = new ConveyorPort(this,
|
||||
// new BeltPortProfile(Position, Direction.Back, 1, PortAccess.BiDirectional),
|
||||
// new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.Start),
|
||||
// (item, offset) =>
|
||||
// {
|
||||
// var obstacle = 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;});
|
||||
// EndPort = new ConveyorPort(this,
|
||||
// new BeltPortProfile(Position, Direction.Front, 1, PortAccess.BiDirectional),
|
||||
// new ItemConveyor.BeltTEnd(ItemConveyor.ConveyorEnd.End),
|
||||
// (item, offset) =>
|
||||
// {
|
||||
// var obstacle = GetDistanceToNextItem(ItemConveyor.BeltDirection.TowardEnd, 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;});
|
||||
//TODO Should liklely account for max search distance where the conveyorm may be needed to know
|
||||
public record class BeltObstacle(float Distance)
|
||||
{
|
||||
// public ObstacleKind Kind { get; }
|
||||
|
||||
// public BeltObstacle()
|
||||
// {
|
||||
// }
|
||||
// public float DistanceToCenter { get; } // always center / reference
|
||||
// public LaneSpan LaneSpan { get; } // only meaningful for Item
|
||||
// public ConveyorSlice? Item { get; } // only if Kind == Item
|
||||
// public IMovementConveyor? Conveyor { get; } // boundary case
|
||||
// public IBeltPort? Port { get; } // Port case
|
||||
|
||||
// public bool IsItem => Kind == ObstacleKind.Item;
|
||||
// public bool IsBoundary => Kind == ObstacleKind.Boundary;
|
||||
}
|
||||
|
||||
// public enum ObstacleKind
|
||||
// {
|
||||
// None,
|
||||
// Item,
|
||||
// Boundary,
|
||||
// }
|
||||
public record ItemBeltObstacle(float Distance, ConveyorSlice Item, LaneSpan LaneSpan) : BeltObstacle(Distance)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public record BoundaryBeltObstacle(float DistanceToBoundary) : BeltObstacle(DistanceToBoundary)
|
||||
{
|
||||
|
||||
}
|
||||
public record PortBeltObstacle(float DistanceToBoundary, LaneSpan LaneSpan, IBeltPort BeltPort) : BoundaryBeltObstacle(DistanceToBoundary)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
1
src/VoxelGrid/TestItemConveyor.cs.uid
Normal file
1
src/VoxelGrid/TestItemConveyor.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://j24fuotdwwx4
|
||||
177
src/VoxelGrid/VoxelGridNode.cs
Normal file
177
src/VoxelGrid/VoxelGridNode.cs
Normal file
@@ -0,0 +1,177 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Chickensoft.AutoInject;
|
||||
using Chickensoft.Introspection;
|
||||
using Godot;
|
||||
using Godot.Collections;
|
||||
using SJK.Functional;
|
||||
|
||||
//Will Liklely be the game instead of a node like this
|
||||
[Meta(typeof(IAutoNode))]// [Tool]
|
||||
public partial class VoxelGridNode : Node3D, IProvide<IVoxelGridQuery<LayeredEquipment>>, IProvide<IEquipmentComponentRegistry>, IProvide<IVoxelGridRegistry>, IProvide<IItemRenderer>
|
||||
{
|
||||
public override void _Notification(int what) => this.Notify(what);
|
||||
private IVoxelGridQuery<LayeredEquipment> _voxelGrid = default!;
|
||||
IVoxelGridQuery<LayeredEquipment> IProvide<IVoxelGridQuery<LayeredEquipment>>.Value() => _voxelGrid;
|
||||
|
||||
private IVoxelGridRegistry _voxelGridRegistry = default!;
|
||||
IVoxelGridRegistry IProvide<IVoxelGridRegistry>.Value() => _voxelGridRegistry;
|
||||
private IItemRenderer _itemRenderer = default!;
|
||||
IItemRenderer IProvide<IItemRenderer>.Value() => _itemRenderer;
|
||||
public override void _Ready()
|
||||
{
|
||||
TestStructural.Test();
|
||||
GD.Print();
|
||||
base._Ready();
|
||||
_voxelGrid = new EquipmentVoxelGrid();
|
||||
_equipmentComponentRegistry = new EquipmentComponentRegistry();
|
||||
_voxelGridRegistry = new VoxelRegistry();
|
||||
_itemRenderer = new TestItemRendered();
|
||||
AddChild(_itemRenderer as Node);
|
||||
Timer timer = new Timer() { WaitTime = .25f, Autostart = true };//TEST
|
||||
AddChild(timer);//TEST
|
||||
// timer.Timeout += _itemRenderer.Tick;//TEST
|
||||
this.Provide();
|
||||
}
|
||||
public override void _Process(double delta)
|
||||
{
|
||||
base._Process(delta);
|
||||
// GD.Print(_voxelGridRegistry.Get<ISlotFace>(Vector3I.Zero).First().GetConnectingFace());
|
||||
}
|
||||
|
||||
IEquipmentComponentRegistry _equipmentComponentRegistry;
|
||||
IEquipmentComponentRegistry IProvide<IEquipmentComponentRegistry>.Value() => _equipmentComponentRegistry;
|
||||
|
||||
}
|
||||
// [Meta, Id("voxel_grid")]
|
||||
public sealed partial class EquipmentVoxelGrid : IVoxelGridQuery<LayeredEquipment>
|
||||
{
|
||||
private System.Collections.Generic.Dictionary<Vector3I, LayeredEquipment> _data = new();
|
||||
public void AddEquipment(Vector3I position, Equipment equipment)
|
||||
{
|
||||
if (!_data.TryGetValue(position, out var layers))
|
||||
{
|
||||
_data[position] = layers = new();
|
||||
}
|
||||
layers.Add(equipment);
|
||||
}
|
||||
//TODO Use Options instead of nu;lable
|
||||
public LayeredEquipment GetVoxel(Vector3I position) => _data.TryGetValue(position, out var result) ? result : new();
|
||||
public IEnumerable<Equipment> GetEquipmentAt(Vector3I position) => GetVoxel(position).GetEquipments();
|
||||
// public
|
||||
// public void SetEquipmentAt(Vector3I position, T value)
|
||||
// {
|
||||
// _data[position] = value;
|
||||
// GD.Print(value);
|
||||
// }
|
||||
|
||||
public LayeredEquipment SetVoxel(Vector3I position, LayeredEquipment value) => _data[position] = value;
|
||||
public bool IsOccupied(Vector3I cell) => _data.ContainsKey(cell);
|
||||
}
|
||||
public interface IVoxelGridQuery<T>
|
||||
{
|
||||
T GetVoxel(Vector3I cell);
|
||||
T SetVoxel(Vector3I cell, T value);
|
||||
bool IsOccupied(Vector3I cell);
|
||||
}
|
||||
|
||||
public class LayeredEquipment
|
||||
{
|
||||
public int Count => _equipment.Count;
|
||||
private readonly HashSet<Equipment> _equipment = new();
|
||||
public IEnumerable<Equipment> GetEquipments() => _equipment;
|
||||
public bool Add(Equipment equipment) => _equipment.Add(equipment);
|
||||
public bool Remove(Equipment equipment) => _equipment.Remove(equipment);
|
||||
public void AddMany(params Equipment[] equipment)
|
||||
{
|
||||
foreach (var item in equipment)
|
||||
{
|
||||
Add(item);
|
||||
}
|
||||
}
|
||||
public void RemoveMany(params Equipment[] equipment)
|
||||
{
|
||||
foreach (var item in equipment)
|
||||
{
|
||||
Remove(item);
|
||||
}
|
||||
}
|
||||
public void IfAny(Action<IEnumerable<Equipment>> action)
|
||||
{
|
||||
if (Count > 0)
|
||||
{
|
||||
action(GetEquipments());
|
||||
}
|
||||
}
|
||||
}
|
||||
public sealed class SlotDescriptor {
|
||||
public SlotDirection Direction { get; }
|
||||
|
||||
public SlotDescriptor(
|
||||
SlotDirection direction
|
||||
)
|
||||
{
|
||||
Direction = direction;
|
||||
}
|
||||
}
|
||||
public abstract class SlotLogic<TPayload> {
|
||||
public SlotDescriptor Descriptor { get; }
|
||||
|
||||
protected SlotLogic(SlotDescriptor descriptor) {
|
||||
Descriptor = descriptor;
|
||||
}
|
||||
|
||||
public abstract bool CanTransfer(
|
||||
TPayload payload
|
||||
);
|
||||
|
||||
public abstract bool TryTransfer(
|
||||
TPayload payload
|
||||
);
|
||||
}
|
||||
public sealed class ItemSlotLogic
|
||||
: SlotLogic<IBeltItem> {
|
||||
|
||||
public ItemSlotLogic(SlotDescriptor descriptor)
|
||||
: base(descriptor) {}
|
||||
|
||||
public override bool CanTransfer(IBeltItem item) => false;
|
||||
// item.Count > 0;
|
||||
|
||||
public override bool TryTransfer(IBeltItem item) {
|
||||
// routing rules
|
||||
return true;
|
||||
}
|
||||
}
|
||||
[Meta]
|
||||
public partial class ItemSlotNode
|
||||
: SlotComponentNode {
|
||||
|
||||
private ItemSlotLogic _logic;
|
||||
|
||||
public override void _Ready() {
|
||||
base._Ready();
|
||||
_logic = new ItemSlotLogic(
|
||||
new SlotDescriptor(Direction)
|
||||
);
|
||||
}
|
||||
public void OnResolved()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void Tick(IBeltItem stack) {
|
||||
if (_logic.CanTransfer(stack))
|
||||
_logic.TryTransfer(stack);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public enum SlotDirection
|
||||
{
|
||||
Input,
|
||||
Output
|
||||
}
|
||||
1
src/VoxelGrid/VoxelGridNode.cs.uid
Normal file
1
src/VoxelGrid/VoxelGridNode.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dcrb286hmpli
|
||||
93
src/VoxelGrid/VoxelGridNode.tscn
Normal file
93
src/VoxelGrid/VoxelGridNode.tscn
Normal file
@@ -0,0 +1,93 @@
|
||||
[gd_scene format=3 uid="uid://dfacxkkkc0v10"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dcrb286hmpli" path="res://src/VoxelGrid/VoxelGridNode.cs" id="1_tsdpe"]
|
||||
[ext_resource type="Script" uid="uid://ee5aoxi8mjnw" path="res://src/VoxelGrid/BeltPort.cs" id="6_2wkfx"]
|
||||
[ext_resource type="PackedScene" uid="uid://c4h7mwnfrdesg" path="res://src/Conveyors/ConveyorBeltStraight/ConveyorBeltStraight.tscn" id="6_mxaon"]
|
||||
|
||||
[sub_resource type="Curve3D" id="Curve3D_mxaon"]
|
||||
_data = {
|
||||
"points": PackedVector3Array(0, 0, 0, 0, 0, 0, 0, 0.5, -0.5, 0, 0, 0, 0, 0, 0, 0, 0.5, 0),
|
||||
"tilts": PackedFloat32Array(0, 0)
|
||||
}
|
||||
point_count = 2
|
||||
|
||||
[node name="VoxelGridNode" type="Node3D" unique_id=825696340]
|
||||
script = ExtResource("1_tsdpe")
|
||||
|
||||
[node name="Camera3D" type="Camera3D" parent="." unique_id=75458542]
|
||||
transform = Transform3D(0.6279494, -0.32487354, 0.70720345, -8.896721e-09, 0.908705, 0.4174389, -0.77825415, -0.26213053, 0.5706208, 4.635174, 2.6038146, 2.739409)
|
||||
|
||||
[node name="ConveyorBeltStraight5" parent="." unique_id=975225936 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 3, 0, 0)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight22" parent="." unique_id=81137442 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 2, 0, 0)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight13" parent="." unique_id=607296315 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 0, 0, 0)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight26" parent="." unique_id=626657643 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 1, 0, 0)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight6" parent="." unique_id=264656404 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -1, 0, 0)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight23" parent="." unique_id=1438072389 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -2, 0, 0)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight14" parent="." unique_id=1045608025 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -4, 0, 0)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight27" parent="." unique_id=899289297 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -3, 0, 0)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight7" parent="." unique_id=1602707514 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 3, 0, 1)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight24" parent="." unique_id=904248251 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 2, 0, 1)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight15" parent="." unique_id=533235542 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 0, 0, 1)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight28" parent="." unique_id=2116943332 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, 1, 0, 1)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight8" parent="." unique_id=1109978762 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -1, 0, 1)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight25" parent="." unique_id=1110001269 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -2, 0, 1)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight16" parent="." unique_id=1178816500 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -4, 0, 1)
|
||||
Width = 1
|
||||
|
||||
[node name="ConveyorBeltStraight29" parent="." unique_id=1537356869 instance=ExtResource("6_mxaon")]
|
||||
transform = Transform3D(1.3113416e-07, 0, -1, 0, 1, 0, 1, 0, 1.3113416e-07, -3, 0, 1)
|
||||
Width = 1
|
||||
|
||||
[node name="Node3D2" type="Node3D" parent="." unique_id=1815100025 node_paths=PackedStringArray("Path")]
|
||||
transform = Transform3D(1.3113416e-07, 0, 1, 0, 1, 0, -1, 0, 1.3113416e-07, 4, 0, 1)
|
||||
script = ExtResource("6_2wkfx")
|
||||
Face = 4
|
||||
Width = 1
|
||||
Access = 3
|
||||
Path = NodePath("Path3D")
|
||||
|
||||
[node name="Path3D" type="Path3D" parent="Node3D2" unique_id=1525648764]
|
||||
curve = SubResource("Curve3D_mxaon")
|
||||
34
test/src/GameTest.cs
Normal file
34
test/src/GameTest.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
namespace ChickenGameTest;
|
||||
|
||||
using System.Threading.Tasks;
|
||||
using Chickensoft.GoDotTest;
|
||||
using Chickensoft.GodotTestDriver;
|
||||
using Chickensoft.GodotTestDriver.Drivers;
|
||||
using Godot;
|
||||
using Shouldly;
|
||||
|
||||
public class GameTest : TestClass
|
||||
{
|
||||
private Game _game = default!;
|
||||
private Fixture _fixture = default!;
|
||||
|
||||
public GameTest(Node testScene) : base(testScene) { }
|
||||
|
||||
[SetupAll]
|
||||
public async Task Setup()
|
||||
{
|
||||
_fixture = new Fixture(TestScene.GetTree());
|
||||
_game = await _fixture.LoadAndAddScene<Game>();
|
||||
}
|
||||
|
||||
[CleanupAll]
|
||||
public void Cleanup() => _fixture.Cleanup();
|
||||
|
||||
[Test]
|
||||
public void TestButtonUpdatesCounter()
|
||||
{
|
||||
var buttonDriver = new ButtonDriver(() => _game.TestButton);
|
||||
buttonDriver.ClickCenter();
|
||||
_game.ButtonPresses.ShouldBe(1);
|
||||
}
|
||||
}
|
||||
1
test/src/GameTest.cs.uid
Normal file
1
test/src/GameTest.cs.uid
Normal file
@@ -0,0 +1 @@
|
||||
uid://dv3ll1ojhcavi
|
||||
Reference in New Issue
Block a user