reinit branch
This commit is contained in:
265
.editorconfig
Normal file
265
.editorconfig
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
# Remove the line below if you want to inherit .editorconfig settings from higher directories
|
||||||
|
root = true
|
||||||
|
|
||||||
|
# C# files
|
||||||
|
[*.cs]
|
||||||
|
|
||||||
|
#### Core EditorConfig Options ####
|
||||||
|
|
||||||
|
# Indentation and spacing
|
||||||
|
indent_size = 4
|
||||||
|
indent_style = space
|
||||||
|
tab_width = 4
|
||||||
|
|
||||||
|
# New line preferences
|
||||||
|
end_of_line = crlf
|
||||||
|
insert_final_newline = false
|
||||||
|
|
||||||
|
#### .NET Code Actions ####
|
||||||
|
|
||||||
|
# Type members
|
||||||
|
dotnet_hide_advanced_members = false
|
||||||
|
dotnet_member_insertion_location = with_other_members_of_the_same_kind
|
||||||
|
dotnet_property_generation_behavior = prefer_throwing_properties
|
||||||
|
|
||||||
|
# Symbol search
|
||||||
|
dotnet_search_reference_assemblies = true
|
||||||
|
|
||||||
|
#### .NET Coding Conventions ####
|
||||||
|
|
||||||
|
# Organize usings
|
||||||
|
dotnet_separate_import_directive_groups = false
|
||||||
|
dotnet_sort_system_directives_first = false
|
||||||
|
file_header_template = unset
|
||||||
|
|
||||||
|
# this. and Me. preferences
|
||||||
|
dotnet_style_qualification_for_event = false
|
||||||
|
dotnet_style_qualification_for_field = false
|
||||||
|
dotnet_style_qualification_for_method = false
|
||||||
|
dotnet_style_qualification_for_property = false
|
||||||
|
|
||||||
|
# Language keywords vs BCL types preferences
|
||||||
|
dotnet_style_predefined_type_for_locals_parameters_members = true
|
||||||
|
dotnet_style_predefined_type_for_member_access = true
|
||||||
|
|
||||||
|
# Parentheses preferences
|
||||||
|
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
|
||||||
|
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
|
||||||
|
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
|
||||||
|
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
|
||||||
|
|
||||||
|
# Modifier preferences
|
||||||
|
dotnet_style_require_accessibility_modifiers = for_non_interface_members
|
||||||
|
|
||||||
|
# Expression-level preferences
|
||||||
|
dotnet_prefer_system_hash_code = true
|
||||||
|
dotnet_style_coalesce_expression = true
|
||||||
|
dotnet_style_collection_initializer = true
|
||||||
|
dotnet_style_explicit_tuple_names = true
|
||||||
|
dotnet_style_namespace_match_folder = true
|
||||||
|
dotnet_style_null_propagation = true
|
||||||
|
dotnet_style_object_initializer = false
|
||||||
|
dotnet_style_operator_placement_when_wrapping = beginning_of_line
|
||||||
|
dotnet_style_prefer_auto_properties = true:warning
|
||||||
|
dotnet_style_prefer_collection_expression = when_types_loosely_match
|
||||||
|
dotnet_style_prefer_compound_assignment = true
|
||||||
|
dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
|
||||||
|
dotnet_style_prefer_conditional_expression_over_return = false:suggestion
|
||||||
|
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
|
||||||
|
dotnet_style_prefer_inferred_anonymous_type_member_names = true
|
||||||
|
dotnet_style_prefer_inferred_tuple_names = true
|
||||||
|
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
|
||||||
|
dotnet_style_prefer_simplified_boolean_expressions = true
|
||||||
|
dotnet_style_prefer_simplified_interpolation = true
|
||||||
|
|
||||||
|
# Field preferences
|
||||||
|
dotnet_style_readonly_field = true
|
||||||
|
|
||||||
|
# Parameter preferences
|
||||||
|
dotnet_code_quality_unused_parameters = all
|
||||||
|
|
||||||
|
# Suppression preferences
|
||||||
|
dotnet_remove_unnecessary_suppression_exclusions = none
|
||||||
|
|
||||||
|
# New line preferences
|
||||||
|
dotnet_style_allow_multiple_blank_lines_experimental = true
|
||||||
|
dotnet_style_allow_statement_immediately_after_block_experimental = true
|
||||||
|
|
||||||
|
#### C# Coding Conventions ####
|
||||||
|
|
||||||
|
# var preferences
|
||||||
|
csharp_style_var_elsewhere = false
|
||||||
|
csharp_style_var_for_built_in_types = true
|
||||||
|
csharp_style_var_when_type_is_apparent = true
|
||||||
|
|
||||||
|
# Expression-bodied members
|
||||||
|
csharp_style_expression_bodied_accessors = true
|
||||||
|
csharp_style_expression_bodied_constructors = false
|
||||||
|
csharp_style_expression_bodied_indexers = true
|
||||||
|
csharp_style_expression_bodied_lambdas = true:suggestion
|
||||||
|
csharp_style_expression_bodied_local_functions = true:suggestion
|
||||||
|
csharp_style_expression_bodied_methods = false
|
||||||
|
csharp_style_expression_bodied_operators = false
|
||||||
|
csharp_style_expression_bodied_properties = true
|
||||||
|
|
||||||
|
# Pattern matching preferences
|
||||||
|
csharp_style_pattern_matching_over_as_with_null_check = true
|
||||||
|
csharp_style_pattern_matching_over_is_with_cast_check = true
|
||||||
|
csharp_style_prefer_extended_property_pattern = true
|
||||||
|
csharp_style_prefer_not_pattern = true
|
||||||
|
csharp_style_prefer_pattern_matching = true:suggestion
|
||||||
|
csharp_style_prefer_switch_expression = true
|
||||||
|
|
||||||
|
# Null-checking preferences
|
||||||
|
csharp_style_conditional_delegate_call = true
|
||||||
|
|
||||||
|
# Modifier preferences
|
||||||
|
csharp_prefer_static_anonymous_function = true
|
||||||
|
csharp_prefer_static_local_function = true
|
||||||
|
csharp_preferred_modifier_order = public,private,protected,internal,file,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,required,volatile,async
|
||||||
|
csharp_style_prefer_readonly_struct = true
|
||||||
|
csharp_style_prefer_readonly_struct_member = true
|
||||||
|
|
||||||
|
# Code-block preferences
|
||||||
|
csharp_prefer_braces = false:warning
|
||||||
|
csharp_prefer_simple_using_statement = true
|
||||||
|
csharp_prefer_system_threading_lock = true
|
||||||
|
csharp_style_namespace_declarations = file_scoped:warning
|
||||||
|
csharp_style_prefer_method_group_conversion = true
|
||||||
|
csharp_style_prefer_primary_constructors = true
|
||||||
|
csharp_style_prefer_simple_property_accessors = true
|
||||||
|
csharp_style_prefer_top_level_statements = true
|
||||||
|
|
||||||
|
# Expression-level preferences
|
||||||
|
csharp_prefer_simple_default_expression = true
|
||||||
|
csharp_style_deconstructed_variable_declaration = true
|
||||||
|
csharp_style_implicit_object_creation_when_type_is_apparent = true
|
||||||
|
csharp_style_inlined_variable_declaration = true
|
||||||
|
csharp_style_prefer_implicitly_typed_lambda_expression = true
|
||||||
|
csharp_style_prefer_index_operator = true
|
||||||
|
csharp_style_prefer_local_over_anonymous_function = true
|
||||||
|
csharp_style_prefer_null_check_over_type_check = true
|
||||||
|
csharp_style_prefer_range_operator = true
|
||||||
|
csharp_style_prefer_tuple_swap = true
|
||||||
|
csharp_style_prefer_unbound_generic_type_in_nameof = true
|
||||||
|
csharp_style_prefer_utf8_string_literals = true
|
||||||
|
csharp_style_throw_expression = true
|
||||||
|
csharp_style_unused_value_assignment_preference = discard_variable
|
||||||
|
csharp_style_unused_value_expression_statement_preference = discard_variable
|
||||||
|
|
||||||
|
# 'using' directive preferences
|
||||||
|
csharp_using_directive_placement = outside_namespace:suggestion
|
||||||
|
|
||||||
|
# New line preferences
|
||||||
|
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true
|
||||||
|
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true
|
||||||
|
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true
|
||||||
|
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true
|
||||||
|
csharp_style_allow_embedded_statements_on_same_line_experimental = true
|
||||||
|
|
||||||
|
#### C# Formatting Rules ####
|
||||||
|
|
||||||
|
# New line preferences
|
||||||
|
csharp_new_line_before_catch = true
|
||||||
|
csharp_new_line_before_else = true
|
||||||
|
csharp_new_line_before_finally = true
|
||||||
|
csharp_new_line_before_members_in_anonymous_types = true
|
||||||
|
csharp_new_line_before_members_in_object_initializers = true
|
||||||
|
csharp_new_line_before_open_brace = all
|
||||||
|
csharp_new_line_between_query_expression_clauses = true
|
||||||
|
|
||||||
|
# Indentation preferences
|
||||||
|
csharp_indent_block_contents = true
|
||||||
|
csharp_indent_braces = false
|
||||||
|
csharp_indent_case_contents = true
|
||||||
|
csharp_indent_case_contents_when_block = true
|
||||||
|
csharp_indent_labels = one_less_than_current
|
||||||
|
csharp_indent_switch_labels = true
|
||||||
|
|
||||||
|
# Space preferences
|
||||||
|
csharp_space_after_cast = false
|
||||||
|
csharp_space_after_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_after_comma = true
|
||||||
|
csharp_space_after_dot = false
|
||||||
|
csharp_space_after_keywords_in_control_flow_statements = true
|
||||||
|
csharp_space_after_semicolon_in_for_statement = true
|
||||||
|
csharp_space_around_binary_operators = before_and_after
|
||||||
|
csharp_space_around_declaration_statements = false
|
||||||
|
csharp_space_before_colon_in_inheritance_clause = true
|
||||||
|
csharp_space_before_comma = false
|
||||||
|
csharp_space_before_dot = false
|
||||||
|
csharp_space_before_open_square_brackets = false
|
||||||
|
csharp_space_before_semicolon_in_for_statement = false
|
||||||
|
csharp_space_between_empty_square_brackets = false
|
||||||
|
csharp_space_between_method_call_empty_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_method_call_name_and_opening_parenthesis = false
|
||||||
|
csharp_space_between_method_call_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_declaration_parameter_list_parentheses = false
|
||||||
|
csharp_space_between_parentheses = false
|
||||||
|
csharp_space_between_square_brackets = false
|
||||||
|
|
||||||
|
# Wrapping preferences
|
||||||
|
csharp_preserve_single_line_blocks = true
|
||||||
|
csharp_preserve_single_line_statements = true
|
||||||
|
|
||||||
|
#### Naming styles ####
|
||||||
|
|
||||||
|
# Naming rules
|
||||||
|
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
|
||||||
|
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i
|
||||||
|
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
|
||||||
|
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_rule.private_consts_should_be_lowercase.severity = suggestion
|
||||||
|
dotnet_naming_rule.private_consts_should_be_lowercase.symbols = private_consts
|
||||||
|
dotnet_naming_rule.private_consts_should_be_lowercase.style = underscore_lower
|
||||||
|
|
||||||
|
dotnet_naming_rule.public_consts_should_be_uppercase.severity = suggestion
|
||||||
|
dotnet_naming_rule.public_consts_should_be_uppercase.symbols = public_consts
|
||||||
|
dotnet_naming_rule.public_consts_should_be_uppercase.style = underscore_upper
|
||||||
|
|
||||||
|
# Symbol specifications
|
||||||
|
|
||||||
|
dotnet_naming_symbols.interface.applicable_kinds = interface
|
||||||
|
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.interface.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
|
||||||
|
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.types.required_modifiers =
|
||||||
|
|
||||||
|
dotnet_naming_symbols.private_consts.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.private_consts.applicable_accessibilities = private, protected_internal, private_protected
|
||||||
|
dotnet_naming_symbols.private_consts.required_modifiers = const
|
||||||
|
|
||||||
|
dotnet_naming_symbols.public_consts.applicable_kinds = field
|
||||||
|
dotnet_naming_symbols.public_consts.applicable_accessibilities = public, internal, protected
|
||||||
|
dotnet_naming_symbols.public_consts.required_modifiers = const
|
||||||
|
|
||||||
|
# Naming styles
|
||||||
|
|
||||||
|
dotnet_naming_style.pascal_case.required_prefix =
|
||||||
|
dotnet_naming_style.pascal_case.required_suffix =
|
||||||
|
dotnet_naming_style.pascal_case.word_separator =
|
||||||
|
dotnet_naming_style.pascal_case.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.begins_with_i.required_prefix = I
|
||||||
|
dotnet_naming_style.begins_with_i.required_suffix =
|
||||||
|
dotnet_naming_style.begins_with_i.word_separator =
|
||||||
|
dotnet_naming_style.begins_with_i.capitalization = pascal_case
|
||||||
|
|
||||||
|
dotnet_naming_style.underscore_lower.required_prefix =
|
||||||
|
dotnet_naming_style.underscore_lower.required_suffix =
|
||||||
|
dotnet_naming_style.underscore_lower.word_separator = _
|
||||||
|
dotnet_naming_style.underscore_lower.capitalization = all_lower
|
||||||
|
|
||||||
|
dotnet_naming_style.underscore_upper.required_prefix =
|
||||||
|
dotnet_naming_style.underscore_upper.required_suffix =
|
||||||
|
dotnet_naming_style.underscore_upper.word_separator = _
|
||||||
|
dotnet_naming_style.underscore_upper.capitalization = all_upper
|
||||||
675
.gitignore
vendored
Normal file
675
.gitignore
vendored
Normal file
@@ -0,0 +1,675 @@
|
|||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/csharp,visualstudio,visualstudiocode,rider
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=csharp,visualstudio,visualstudiocode,rider
|
||||||
|
|
||||||
|
### Csharp ###
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.tlog
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||||
|
*.vbp
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||||
|
*.dsw
|
||||||
|
*.dsp
|
||||||
|
|
||||||
|
# Visual Studio 6 technical files
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# Visual Studio History (VSHistory) files
|
||||||
|
.vshistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
|
|
||||||
|
# VS Code files for those working on multiple tools
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Windows Installer files from build outputs
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
### Rider ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# AWS User-specific
|
||||||
|
.idea/**/aws.xml
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# SonarLint plugin
|
||||||
|
.idea/sonarlint/
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### VisualStudioCode ###
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
|
||||||
|
### VisualStudioCode Patch ###
|
||||||
|
# Ignore all local history of files
|
||||||
|
.history
|
||||||
|
.ionide
|
||||||
|
|
||||||
|
### VisualStudio ###
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
|
||||||
|
# Others
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||||
|
|
||||||
|
# Visual Studio 6 technical files
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
|
||||||
|
# Visual Studio History (VSHistory) files
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
|
||||||
|
# VS Code files for those working on multiple tools
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
|
||||||
|
# Windows Installer files from build outputs
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
|
||||||
|
### VisualStudio Patch ###
|
||||||
|
# Additional files built by Visual Studio
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/csharp,visualstudio,visualstudiocode,rider
|
||||||
1355
.timetracker
Normal file
1355
.timetracker
Normal file
File diff suppressed because it is too large
Load Diff
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"dotnet.defaultSolution": "OrekiWoofsBeehives.slnx"
|
||||||
|
}
|
||||||
88
LICENSE.txt
Normal file
88
LICENSE.txt
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
OrekiWoofs Beehives Mod License (Custom)
|
||||||
|
Version 1.0 - February 4, 2026
|
||||||
|
|
||||||
|
Copyright (c) 2026 OrekiWoof (HoutarouOreki on github.com, OrekiWoof on mods.vintagestory.at, thezjarek@gmail.com)
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
This license applies only to the Source Code.
|
||||||
|
|
||||||
|
1) Definitions
|
||||||
|
"Source Code" means the OrekiWoofs Beehives mod source code and associated
|
||||||
|
documentation contained in this repository or released as part of a mod package.
|
||||||
|
|
||||||
|
"Mod Author" means OrekiWoof (HoutarouOreki on github.com, OrekiWoof on mods.vintagestory.at, email: thezjarek@gmail.com).
|
||||||
|
|
||||||
|
"Game" means Vintage Story.
|
||||||
|
|
||||||
|
"Mod Release" means a .zip file uploaded to https://mods.vintagestory.at as a mod release.
|
||||||
|
|
||||||
|
"Major or Breaking Game Version" means a new Game version that either (a) increments
|
||||||
|
the major version number, or (b) causes the Mod to fail to function as intended.
|
||||||
|
|
||||||
|
"Expressed Intent" means a public statement by the Mod Author indicating they plan to
|
||||||
|
update the Mod for the new Major or Breaking Game Version. This may appear in the Mod
|
||||||
|
description, release notes, or an official support thread linked from the Mod page.
|
||||||
|
|
||||||
|
"Abandonment Trigger" occurs when a Major or Breaking Game Version is released and,
|
||||||
|
within 14 days of that release, the Mod Author neither publishes an update compatible
|
||||||
|
with that version nor provides Expressed Intent to do so.
|
||||||
|
|
||||||
|
"Code Analysis Service" means any service that uploads, indexes, scans, or otherwise
|
||||||
|
analyzes code, including but not limited to public code hosting platforms, automated
|
||||||
|
code review services, and AI-assisted code analysis services.
|
||||||
|
|
||||||
|
2) Ownership
|
||||||
|
The Mod Author retains all right, title, and interest in the Source Code. This license
|
||||||
|
grants only the permissions explicitly stated below.
|
||||||
|
|
||||||
|
3) General Permission (Standard Use)
|
||||||
|
You may redistribute the Source Code only inside a Mod Release, and only in .zip format
|
||||||
|
uploaded to https://mods.vintagestory.at.
|
||||||
|
|
||||||
|
4) Source Code Distribution Restrictions
|
||||||
|
You may not distribute the Source Code in any way except inside a Mod Release (.zip)
|
||||||
|
uploaded to https://mods.vintagestory.at.
|
||||||
|
|
||||||
|
You may not upload the Source Code or any portion of it to any Code Analysis
|
||||||
|
Service, including services that host or analyze code such as GitHub, GitLab, Bitbucket,
|
||||||
|
or similar platforms.
|
||||||
|
|
||||||
|
5) AI Training and Public Availability Restrictions
|
||||||
|
You may not use the Source Code (including documentation) to train, fine-tune, or
|
||||||
|
create datasets for any AI or machine learning system.
|
||||||
|
|
||||||
|
You may not upload, mirror, or otherwise make the Source Code publicly available in
|
||||||
|
any location or service that enables AI training or allows the Source Code to be
|
||||||
|
publicly accessed or indexed.
|
||||||
|
|
||||||
|
6) Conditional Continuation License (After Abandonment Trigger)
|
||||||
|
If the Abandonment Trigger occurs, any person may:
|
||||||
|
a) create fixed or updated versions of the Source Code, and
|
||||||
|
b) distribute those versions only as Mod Releases (.zip) on
|
||||||
|
https://mods.vintagestory.at.
|
||||||
|
|
||||||
|
This conditional permission is subject to all restrictions in Sections 4 and 5 and
|
||||||
|
the following conditions:
|
||||||
|
- You must provide clear attribution to the Mod Author.
|
||||||
|
- You must state that your version is an unofficial continuation.
|
||||||
|
- You must include this license text in your Mod Release.
|
||||||
|
- You must not imply endorsement by the Mod Author.
|
||||||
|
|
||||||
|
If the Mod Author resumes updates or provides Expressed Intent, this conditional
|
||||||
|
permission ends for future releases. Mod Releases already published under this
|
||||||
|
permission may remain available.
|
||||||
|
|
||||||
|
7) No Additional Rights
|
||||||
|
No rights are granted for commercial use, sublicensing, or distribution outside the
|
||||||
|
permissions stated in this license.
|
||||||
|
|
||||||
|
8) Termination
|
||||||
|
Any violation of this license automatically terminates all permissions granted herein.
|
||||||
|
|
||||||
|
9) Game Terms Precedence
|
||||||
|
This license is subject to the Game's terms of service and EULA. In case of conflict,
|
||||||
|
the Game's terms control.
|
||||||
|
|
||||||
|
10) Disclaimer
|
||||||
|
The Source Code is provided "as is" without warranty of any kind, express or implied.
|
||||||
|
The Mod Author is not liable for any damages arising from the use of the Source Code.
|
||||||
18
OrekiWoofsBeehives.slnx
Normal file
18
OrekiWoofsBeehives.slnx
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<Solution>
|
||||||
|
<Configurations>
|
||||||
|
<BuildType Name="Debug22" />
|
||||||
|
<BuildType Name="Debug" />
|
||||||
|
<BuildType Name="Release" />
|
||||||
|
</Configurations>
|
||||||
|
<Folder Name="/Solution Items/">
|
||||||
|
<File Path=".editorconfig" />
|
||||||
|
</Folder>
|
||||||
|
<Project Path="OrekiWoofsBeehives/OrekiWoofsBeehives.csproj" />
|
||||||
|
<Project Path="OrekiWoofsBees.Common/OrekiWoofsBees.Common.csproj" Id="e53bd51a-4c0e-4482-9e20-1361d42e72ea">
|
||||||
|
<BuildType Solution="Debug22|*" Project="Release" />
|
||||||
|
</Project>
|
||||||
|
<Project Path="RoamingBees/RoamingBees/RoamingBees.csproj" Id="7f545210-d3d4-4232-b6d6-7a10be19290c" />
|
||||||
|
<Project Path="RoamingBees/ZZCakeBuild/RoamingBeesCakeBuild.csproj" Id="742949f3-29ab-48ef-bb9a-903055795dfb" />
|
||||||
|
<Project Path="ZZCakeBuild/OrekiWoofsBeehivesCakeBuild.csproj" />
|
||||||
|
<Project Path="OrekiWoofsBees.Full/OrekiWoofsBees.Full.csproj" />
|
||||||
|
</Solution>
|
||||||
244
OrekiWoofsBeehives/Behaviors/BlockBehaviorBeehiveAffected.cs
Normal file
244
OrekiWoofsBeehives/Behaviors/BlockBehaviorBeehiveAffected.cs
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
using OrekiWoofsBeehives.BlockEntities;
|
||||||
|
using OrekiWoofsBeehives.Utilities;
|
||||||
|
using OrekiWoofsBees.Common;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.Config;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
using Vintagestory.GameContent;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives.Behaviors;
|
||||||
|
|
||||||
|
public class BlockBehaviorBeehiveAffected(Block block) : BlockBehavior(block)
|
||||||
|
{
|
||||||
|
private const float population_curve_k = 10f;
|
||||||
|
|
||||||
|
public override string GetPlacedBlockInfo(IWorldAccessor world, BlockPos pos, IPlayer forPlayer)
|
||||||
|
{
|
||||||
|
var isCrop = PlantRecognitionUtilities.IsCrop(block);
|
||||||
|
var isFlower = PlantRecognitionUtilities.IsFlower(block, world.BlockAccessor, pos);
|
||||||
|
var isFarmland = block is BlockFarmland;
|
||||||
|
|
||||||
|
if (!isFarmland && !isCrop && !isFlower)
|
||||||
|
return base.GetPlacedBlockInfo(world, pos, forPlayer);
|
||||||
|
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
var registry = world.Api.GetOrekiWoofsBeehives()?.BeehiveRegistry;
|
||||||
|
|
||||||
|
if (registry is null)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
int beehiveCount = CountNearbyBeehives(world, pos);
|
||||||
|
|
||||||
|
if (beehiveCount <= 0)
|
||||||
|
{
|
||||||
|
if (!isFarmland)
|
||||||
|
return base.GetPlacedBlockInfo(world, pos, forPlayer);
|
||||||
|
|
||||||
|
var baseText = base.GetPlacedBlockInfo(world, pos, forPlayer);
|
||||||
|
var pollinationText = GetPollinatedOnlyText(world, pos);
|
||||||
|
return string.IsNullOrEmpty(pollinationText)
|
||||||
|
? baseText
|
||||||
|
: baseText + pollinationText;
|
||||||
|
}
|
||||||
|
|
||||||
|
float totalEffectiveness = GetBeehiveBoostEffectiveness(world.Api, registry.BeehivePositions, pos);
|
||||||
|
|
||||||
|
var growthOrPlantText = isFarmland
|
||||||
|
? GetLocalizedFarmlandBonusText(world, pos, cfg, totalEffectiveness)
|
||||||
|
: " " + Lang.Get("orekiwoofsbeehives:blockinfo-as-a-plant", GetLocalizedPlantTypeText(isFlower, isCrop));
|
||||||
|
|
||||||
|
var inRangeText = Lang.Get("orekiwoofsbeehives:blockinfo-in-range-beehive", beehiveCount);
|
||||||
|
return inRangeText + growthOrPlantText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetLocalizedPlantTypeText(bool isFlower, bool isCrop)
|
||||||
|
{
|
||||||
|
var plantTypes = new List<string>(2);
|
||||||
|
|
||||||
|
if (isFlower)
|
||||||
|
plantTypes.Add(Lang.Get("orekiwoofsbeehives:blockinfo-flower"));
|
||||||
|
|
||||||
|
if (isCrop)
|
||||||
|
plantTypes.Add(Lang.Get("orekiwoofsbeehives:blockinfo-crop"));
|
||||||
|
|
||||||
|
return string.Join(" ", plantTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetLocalizedFarmlandBonusText(IWorldAccessor world, BlockPos farmlandPos, Config cfg, float totalEffectiveness)
|
||||||
|
{
|
||||||
|
var parts = new List<string>();
|
||||||
|
|
||||||
|
if (cfg.SpeedBoost)
|
||||||
|
{
|
||||||
|
var speedPercent = (cfg.GrowthSpeedBonus * totalEffectiveness * 100f).ToString("N0", CultureInfo.InvariantCulture);
|
||||||
|
parts.Add(Lang.Get("orekiwoofsbeehives:blockinfo-growth-speed-bonus", speedPercent));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfg.YieldBoost)
|
||||||
|
{
|
||||||
|
var yieldBonusFactor = Math.Max(0f, cfg.YieldMultiplier - 1f);
|
||||||
|
var yieldPercent = (yieldBonusFactor * totalEffectiveness * 100f).ToString("N0", CultureInfo.InvariantCulture);
|
||||||
|
parts.Add(Lang.Get("orekiwoofsbeehives:blockinfo-yield-bonus", yieldPercent));
|
||||||
|
}
|
||||||
|
|
||||||
|
var bonusText = parts.Count > 0
|
||||||
|
? $" (+{string.Join(", +", parts)})"
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
|
var pollinationText = GetPollinationText(world, farmlandPos);
|
||||||
|
return bonusText + pollinationText;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetPollinationText(IWorldAccessor world, BlockPos farmlandPos)
|
||||||
|
{
|
||||||
|
var cropPos = farmlandPos.UpCopy();
|
||||||
|
if (world.BlockAccessor.GetBlock(cropPos) is not BlockCrop crop)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var tracker = world.BlockAccessor.GetBlock(farmlandPos)
|
||||||
|
.GetBEBehavior<BlockEntityBehaviorBeehiveYieldMultiplier>(farmlandPos);
|
||||||
|
if (tracker is null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var currentStage = crop.CurrentCropStage;
|
||||||
|
var remainingStages = tracker.GetRemainingPollinationStages(currentStage);
|
||||||
|
|
||||||
|
if (remainingStages <= 0)
|
||||||
|
return '\n' + Lang.Get("orekiwoofsbeehives:blockinfo-pollinated");
|
||||||
|
|
||||||
|
return '\n' + Lang.Get("orekiwoofsbeehives:blockinfo-will-be-pollinated", remainingStages);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetPollinatedOnlyText(IWorldAccessor world, BlockPos farmlandPos)
|
||||||
|
{
|
||||||
|
var cropPos = farmlandPos.UpCopy();
|
||||||
|
if (world.BlockAccessor.GetBlock(cropPos) is not BlockCrop crop)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var tracker = world.BlockAccessor.GetBlock(farmlandPos)
|
||||||
|
.GetBEBehavior<BlockEntityBehaviorBeehiveYieldMultiplier>(farmlandPos);
|
||||||
|
if (tracker is null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return tracker.IsPollinated(crop.CurrentCropStage)
|
||||||
|
? '\n' + Lang.Get("orekiwoofsbeehives:blockinfo-pollinated")
|
||||||
|
: string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBlockPlaced(IWorldAccessor world, BlockPos pos, ref EnumHandling handling)
|
||||||
|
{
|
||||||
|
if (world.Side.IsServer() && PlantRecognitionUtilities.IsPlant(block, world.BlockAccessor, pos))
|
||||||
|
world.Api.GetPlantPositionRegistry()?.AddPlantPosition(pos, block);
|
||||||
|
|
||||||
|
base.OnBlockPlaced(world, pos, ref handling);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBlockRemoved(IWorldAccessor world, BlockPos pos, ref EnumHandling handling)
|
||||||
|
{
|
||||||
|
if (PlantRecognitionUtilities.IsPlant(block, world.BlockAccessor, pos))
|
||||||
|
world.Api.GetPlantPositionRegistry()?.RemovePlantPosition(pos, block);
|
||||||
|
|
||||||
|
base.OnBlockRemoved(world, pos, ref handling);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNeighbourBlockChange(IWorldAccessor world, BlockPos pos, BlockPos neibpos, ref EnumHandling handling)
|
||||||
|
{
|
||||||
|
if (world.Side.IsServer() && block is BlockFarmland && neibpos.X == pos.X && neibpos.Y == pos.Y + 1 && neibpos.Z == pos.Z)
|
||||||
|
{
|
||||||
|
var tracker = block.GetBEBehavior<BlockEntityBehaviorBeehiveYieldMultiplier>(pos);
|
||||||
|
tracker?.OnUpperNeighborChanged(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnNeighbourBlockChange(world, pos, neibpos, ref handling);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int CountNearbyBeehives(IWorldAccessor world, BlockPos pos)
|
||||||
|
{
|
||||||
|
var count = world.Api.GetPlantPositionRegistry()?.CountBeehivesInRadius(pos, Config.Instance.BeehiveRadius);
|
||||||
|
|
||||||
|
return count ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float GetBeehiveBoostEffectiveness(ICoreAPI api, IEnumerable<StructVec3i> beehivePositions, BlockPos plantPos)
|
||||||
|
{
|
||||||
|
if (api is null)
|
||||||
|
return 1f;
|
||||||
|
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
int radius = cfg.BeehiveRadius;
|
||||||
|
int clampedFullBoostDistance = Math.Clamp(cfg.PlantsFullBoostDistanceToHive, 1, radius);
|
||||||
|
int radiusDelta = Math.Max(1, radius - clampedFullBoostDistance);
|
||||||
|
var combinationType = cfg.GetMultipleBeehivesBoostCombinationType();
|
||||||
|
float totalEffectiveness = 0f;
|
||||||
|
float maxEffectiveness = 0f;
|
||||||
|
|
||||||
|
foreach (var beehivePos in beehivePositions)
|
||||||
|
{
|
||||||
|
if (!Overlaps.IsWithinSphericalRadius(plantPos, beehivePos, radius))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var blockPos = new BlockPos(beehivePos.X, beehivePos.Y, beehivePos.Z, plantPos.dimension);
|
||||||
|
if (api.World.BlockAccessor.GetBlockEntity(blockPos) is not BlockEntityReusableBeehive beehive)
|
||||||
|
continue;
|
||||||
|
if (beehive.BeePopulation < cfg.MinBeesForPlantBoost)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float populationScale = GetPopulationBoostScale(beehive.BeePopulation, cfg);
|
||||||
|
if (populationScale <= 0f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
float distance = blockPos.DistanceTo(plantPos);
|
||||||
|
float distanceEffectiveness = distance <= clampedFullBoostDistance
|
||||||
|
? 1f
|
||||||
|
: Math.Clamp(1f - ((distance - clampedFullBoostDistance) / radiusDelta), 0f, 1f);
|
||||||
|
float effectiveness = distanceEffectiveness * populationScale;
|
||||||
|
|
||||||
|
if (combinationType == MultipleBeehivesBoostCombinationType.Max)
|
||||||
|
{
|
||||||
|
maxEffectiveness = Math.Max(maxEffectiveness, effectiveness);
|
||||||
|
if (maxEffectiveness >= 1f)
|
||||||
|
return 1f;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
totalEffectiveness += effectiveness;
|
||||||
|
if (totalEffectiveness >= 1f)
|
||||||
|
return 1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (combinationType == MultipleBeehivesBoostCombinationType.Max)
|
||||||
|
return Math.Min(maxEffectiveness, 1f);
|
||||||
|
|
||||||
|
return Math.Min(totalEffectiveness, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float GetPopulationBoostScale(double beePopulation, Config cfg)
|
||||||
|
{
|
||||||
|
if (beePopulation < cfg.MinBeesForPlantBoost)
|
||||||
|
return 0f;
|
||||||
|
|
||||||
|
if (cfg.GetBoostCalculationType() == BoostCalculationType.Full)
|
||||||
|
return 1f;
|
||||||
|
|
||||||
|
float minPopulation = cfg.MinBeesForPlantBoost;
|
||||||
|
float maxPopulation = cfg.MaxBeePopulation * (cfg.PopulationPercentForMaxBoost / 100f);
|
||||||
|
|
||||||
|
if (maxPopulation <= minPopulation)
|
||||||
|
return 1f;
|
||||||
|
|
||||||
|
float x = (float)Math.Clamp((beePopulation - minPopulation) / (maxPopulation - minPopulation), 0d, 1d);
|
||||||
|
|
||||||
|
if (cfg.GetBoostCalculationType() == BoostCalculationType.LinearPopulation)
|
||||||
|
return x;
|
||||||
|
|
||||||
|
float numerator = MathF.Log(1f + (population_curve_k * x));
|
||||||
|
float denominator = MathF.Log(1f + population_curve_k);
|
||||||
|
if (denominator <= 0f)
|
||||||
|
return x;
|
||||||
|
|
||||||
|
return Math.Clamp(numerator / denominator, 0f, 1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
using OrekiWoofsBeehives.Utilities;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.Datastructures;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
using Vintagestory.GameContent;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives.Behaviors;
|
||||||
|
|
||||||
|
public class BlockEntityBehaviorBeehiveYieldMultiplier(BlockEntity blockEntity) : BlockEntityBehavior(blockEntity)
|
||||||
|
{
|
||||||
|
private const string stages_attribute = "orekiwoofsbeehives_stagesWithBeehivePresent";
|
||||||
|
public const int REQUIRED_POLLINATION_STAGES = 2;
|
||||||
|
private readonly HashSet<int> stagesWithBeehivePresent = [];
|
||||||
|
|
||||||
|
public IReadOnlyCollection<int> StagesWithBeehivePresent => stagesWithBeehivePresent;
|
||||||
|
|
||||||
|
public override void Initialize(ICoreAPI api, JsonObject properties)
|
||||||
|
{
|
||||||
|
base.Initialize(api, properties);
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
var interval = 1000;
|
||||||
|
#else
|
||||||
|
var interval = 10000;
|
||||||
|
#endif
|
||||||
|
if (api.Side == EnumAppSide.Server)
|
||||||
|
Blockentity.RegisterGameTickListener(OnServerTick, interval);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ToTreeAttributes(ITreeAttribute tree)
|
||||||
|
{
|
||||||
|
base.ToTreeAttributes(tree);
|
||||||
|
|
||||||
|
if (stagesWithBeehivePresent.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string serialized = string.Join(",", stagesWithBeehivePresent.OrderBy(x => x));
|
||||||
|
tree.SetString(stages_attribute, serialized);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FromTreeAttributes(ITreeAttribute tree, IWorldAccessor worldAccessForResolve)
|
||||||
|
{
|
||||||
|
base.FromTreeAttributes(tree, worldAccessForResolve);
|
||||||
|
|
||||||
|
stagesWithBeehivePresent.Clear();
|
||||||
|
|
||||||
|
var serialized = tree.GetString(stages_attribute);
|
||||||
|
if (string.IsNullOrWhiteSpace(serialized))
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (string stageValue in serialized.Split(','))
|
||||||
|
{
|
||||||
|
if (int.TryParse(stageValue, out int stage))
|
||||||
|
stagesWithBeehivePresent.Add(stage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnServerTick(float dt)
|
||||||
|
{
|
||||||
|
if (Blockentity.Block is not BlockFarmland)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BlockPos cropPos = Blockentity.Pos.UpCopy();
|
||||||
|
if (Api.World.BlockAccessor.GetBlock(cropPos) is not BlockCrop crop)
|
||||||
|
{
|
||||||
|
ClearTracking();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int stage = crop.CurrentCropStage;
|
||||||
|
if (stage < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (stagesWithBeehivePresent.Count > 0 && stage < stagesWithBeehivePresent.Max())
|
||||||
|
stagesWithBeehivePresent.Clear();
|
||||||
|
|
||||||
|
var modSystem = Api.GetOrekiWoofsBeehives();
|
||||||
|
if (modSystem?.BeehiveRegistry is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float effectiveness = BlockBehaviorBeehiveAffected.GetBeehiveBoostEffectiveness(Api, modSystem.BeehiveRegistry.BeehivePositions, cropPos);
|
||||||
|
if (effectiveness <= 0f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
stagesWithBeehivePresent.Add(stage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnUpperNeighborChanged(IWorldAccessor world)
|
||||||
|
{
|
||||||
|
if (Blockentity.Block is not BlockFarmland)
|
||||||
|
return;
|
||||||
|
|
||||||
|
BlockPos cropPos = Blockentity.Pos.UpCopy();
|
||||||
|
if (world.BlockAccessor.GetBlock(cropPos) is not BlockCrop crop)
|
||||||
|
{
|
||||||
|
ClearTracking();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int stage = crop.CurrentCropStage;
|
||||||
|
if (stage >= 0 && stagesWithBeehivePresent.Count > 0 && stage < stagesWithBeehivePresent.Max())
|
||||||
|
ClearTracking();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearTracking()
|
||||||
|
{
|
||||||
|
if (stagesWithBeehivePresent.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
stagesWithBeehivePresent.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetCoveredStagesCount(int currentStage)
|
||||||
|
{
|
||||||
|
if (currentStage < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var coveredStages = 0;
|
||||||
|
foreach (var stage in stagesWithBeehivePresent)
|
||||||
|
{
|
||||||
|
if (stage >= 0 && stage <= currentStage)
|
||||||
|
coveredStages++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return coveredStages;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetRemainingPollinationStages(int currentStage)
|
||||||
|
{
|
||||||
|
var coveredStages = GetCoveredStagesCount(currentStage);
|
||||||
|
return System.Math.Max(0, REQUIRED_POLLINATION_STAGES - coveredStages);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsPollinated(int currentStage)
|
||||||
|
{
|
||||||
|
return GetRemainingPollinationStages(currentStage) == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using OrekiWoofsBeehives.Utilities;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.Datastructures;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives.Behaviors;
|
||||||
|
|
||||||
|
public class BlockEntityBehaviorVanillaSkepSwarmTarget(BlockEntity blockEntity) : BlockEntityBehavior(blockEntity)
|
||||||
|
{
|
||||||
|
public override void Initialize(ICoreAPI api, JsonObject properties)
|
||||||
|
{
|
||||||
|
base.Initialize(api, properties);
|
||||||
|
|
||||||
|
if (api.Side.IsServer())
|
||||||
|
TryRegister(api.World, Blockentity.Pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBlockRemoved()
|
||||||
|
{
|
||||||
|
base.OnBlockRemoved();
|
||||||
|
|
||||||
|
if (Api.Side.IsServer())
|
||||||
|
Api.GetOrekiWoofsBeehives()?.VanillaSkepRegistry.UnregisterVanillaSkep(Blockentity.Pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBlockUnloaded()
|
||||||
|
{
|
||||||
|
base.OnBlockUnloaded();
|
||||||
|
|
||||||
|
if (Api.Side.IsServer())
|
||||||
|
Api.GetOrekiWoofsBeehives()?.VanillaSkepRegistry.UnregisterVanillaSkep(Blockentity.Pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TryRegister(IWorldAccessor world, Vintagestory.API.MathTools.BlockPos pos)
|
||||||
|
{
|
||||||
|
var currentBlock = world.BlockAccessor.GetBlock(pos);
|
||||||
|
var path = currentBlock?.Code?.Path;
|
||||||
|
if (path == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var populatedCode = GetPopulatedSkepCode(path);
|
||||||
|
if (populatedCode == null)
|
||||||
|
{
|
||||||
|
world.Api.GetOrekiWoofsBeehives()?.VanillaSkepRegistry.UnregisterVanillaSkep(pos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
world.Api.GetOrekiWoofsBeehives()?.VanillaSkepRegistry.RegisterVanillaSkep(pos, populatedCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? GetPopulatedSkepCode(string path)
|
||||||
|
{
|
||||||
|
if (path.StartsWith("skep-reed-empty-"))
|
||||||
|
return $"game:{path.Replace("-empty-", "-populated-")}";
|
||||||
|
|
||||||
|
if (path.StartsWith("skep-papyrus-empty-"))
|
||||||
|
return $"game:{path.Replace("-empty-", "-populated-")}";
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
193
OrekiWoofsBeehives/BlockEntities/BeehiveStats.cs
Normal file
193
OrekiWoofsBeehives/BlockEntities/BeehiveStats.cs
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
using System;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives.BlockEntities;
|
||||||
|
|
||||||
|
public readonly record struct BeehiveStats
|
||||||
|
{
|
||||||
|
public required int DailyNetPopulationChange { get; init; }
|
||||||
|
public required double FramesPerDay { get; init; }
|
||||||
|
public required double FeedConsumedPerDay { get; init; }
|
||||||
|
public required double FlowerProductionMultiplier { get; init; }
|
||||||
|
public required double BeeProductionMultiplier { get; init; }
|
||||||
|
public required BeehiveStatsComponents Components { get; init; }
|
||||||
|
|
||||||
|
public static BeehiveStats Create(BlockEntityReusableBeehive beehive, bool isGreenhouse, double? totalDaysDate = null)
|
||||||
|
{
|
||||||
|
ClimateCondition climate = totalDaysDate.HasValue
|
||||||
|
? beehive.Api.World.BlockAccessor.GetClimateAt(beehive.Pos, EnumGetClimateMode.ForSuppliedDateValues, totalDaysDate.Value)
|
||||||
|
: beehive.Api.World.BlockAccessor.GetClimateAt(beehive.Pos, EnumGetClimateMode.NowValues);
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
float currentTemperature = GetTemperature(climate, isGreenhouse && cfg.GreenhouseAffectsBeehive);
|
||||||
|
int dailyDeaths = CalculateDailyDeaths(beehive, currentTemperature);
|
||||||
|
double dailyGrowthRate = CalculateDailyGrowthRate(beehive);
|
||||||
|
int filledFramesCount = beehive.CountFilledFrames();
|
||||||
|
double emptyFrameMultiplier = GetEmptyFrameMultiplier(beehive.CountEmptyFrames());
|
||||||
|
int dailyFilledFrameBonus = (int)(filledFramesCount * cfg.BonusGrowthPerFilledFrame * emptyFrameMultiplier);
|
||||||
|
int dailyPercentageGrowth = (int)(beehive.BeePopulation * dailyGrowthRate);
|
||||||
|
double temperatureMultiplier = GetTemperatureMultiplier(currentTemperature);
|
||||||
|
int dailyGrowth = (int)((dailyPercentageGrowth + dailyFilledFrameBonus) * temperatureMultiplier);
|
||||||
|
double framesPerDay = CalculateFramesPerDay(beehive, temperatureMultiplier);
|
||||||
|
double feedConsumedPerDay = CalculateFeedConsumptionPerDay(beehive, currentTemperature, temperatureMultiplier);
|
||||||
|
int emptyFrames = beehive.CountEmptyFrames();
|
||||||
|
int totalFrames = beehive.CountTotalFrames();
|
||||||
|
double beeProductionMultiplier = beehive.BeePopulation / cfg.ReferenceBees;
|
||||||
|
double effectiveFlowers = GetEffectiveFlowers(beehive);
|
||||||
|
double flowerProductionMultiplier = effectiveFlowers / cfg.ReferenceFlowers;
|
||||||
|
|
||||||
|
int dailyNetPopulationChange = dailyGrowth - dailyDeaths;
|
||||||
|
return new BeehiveStats
|
||||||
|
{
|
||||||
|
DailyNetPopulationChange = dailyNetPopulationChange,
|
||||||
|
BeeProductionMultiplier = beeProductionMultiplier,
|
||||||
|
FlowerProductionMultiplier = flowerProductionMultiplier,
|
||||||
|
FramesPerDay = framesPerDay,
|
||||||
|
FeedConsumedPerDay = feedConsumedPerDay,
|
||||||
|
Components = new(
|
||||||
|
dailyDeaths,
|
||||||
|
dailyGrowthRate,
|
||||||
|
dailyPercentageGrowth,
|
||||||
|
filledFramesCount,
|
||||||
|
emptyFrames,
|
||||||
|
totalFrames,
|
||||||
|
dailyFilledFrameBonus,
|
||||||
|
dailyGrowth,
|
||||||
|
temperatureMultiplier,
|
||||||
|
effectiveFlowers,
|
||||||
|
isGreenhouse,
|
||||||
|
currentTemperature
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int CalculateDailyDeaths(BlockEntityReusableBeehive beehive, float currentTemperature)
|
||||||
|
{
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
var missingFlowers = Math.Max(0, cfg.FlowerThreshold - GetEffectiveFlowers(beehive));
|
||||||
|
var winterReverseRamp = GetWinterReverseRamp(currentTemperature);
|
||||||
|
var winterRamp = 1 - winterReverseRamp;
|
||||||
|
int baseDeaths = (int)(cfg.BaseDeathsPerDay * winterRamp);
|
||||||
|
int flowerDeaths = (int)(missingFlowers * cfg.DeathPerMissingFlower * winterRamp);
|
||||||
|
int dailyDeaths = baseDeaths + flowerDeaths;
|
||||||
|
|
||||||
|
bool hasFilledOrFeedFrames = beehive.CountFilledFrames() > 0;
|
||||||
|
if (cfg.WinterHardMode && !hasFilledOrFeedFrames && winterReverseRamp > 0)
|
||||||
|
dailyDeaths += (int)(cfg.WinterDailyBeeDeathsWithoutFood * winterReverseRamp);
|
||||||
|
|
||||||
|
return dailyDeaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double CalculateDailyGrowthRate(BlockEntityReusableBeehive beehive)
|
||||||
|
{
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
double baseGrowthRate = Math.Pow(2, 1.0 / cfg.DoublingTimeDays) - 1;
|
||||||
|
|
||||||
|
double emptyFrameMultiplier = GetEmptyFrameMultiplier(beehive.CountEmptyFrames());
|
||||||
|
double growthModifier = emptyFrameMultiplier;
|
||||||
|
|
||||||
|
return baseGrowthRate * growthModifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double GetEmptyFrameMultiplier(int emptyFrames)
|
||||||
|
{
|
||||||
|
var hasEmptyFrames = emptyFrames > 0;
|
||||||
|
return hasEmptyFrames ? 0.5 : 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetTemperature(ClimateCondition climate, bool isGreenhouse)
|
||||||
|
{
|
||||||
|
float currentTemperature = climate?.Temperature ?? 20f;
|
||||||
|
if (isGreenhouse)
|
||||||
|
currentTemperature += 5;
|
||||||
|
return currentTemperature;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double GetTemperatureMultiplier(float currentTemperature)
|
||||||
|
{
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
|
||||||
|
if (currentTemperature >= cfg.MaxTemperatureGrowth)
|
||||||
|
return 1.0;
|
||||||
|
if (currentTemperature <= cfg.MinTemperatureGrowth)
|
||||||
|
return 0.0;
|
||||||
|
|
||||||
|
float temperatureRange = cfg.MaxTemperatureGrowth - cfg.MinTemperatureGrowth;
|
||||||
|
return (currentTemperature - cfg.MinTemperatureGrowth) / temperatureRange;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double CalculateFramesPerDay(BlockEntityReusableBeehive beehive, double temperatureMultiplier)
|
||||||
|
{
|
||||||
|
if (beehive.BeePopulation <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
double effectiveFlowers = GetEffectiveFlowers(beehive);
|
||||||
|
if (effectiveFlowers <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
double flowerFactor = effectiveFlowers / cfg.ReferenceFlowers;
|
||||||
|
double beeFactor = beehive.BeePopulation / cfg.ReferenceBees;
|
||||||
|
|
||||||
|
return flowerFactor * beeFactor * temperatureMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double CalculateFeedConsumptionPerDay(BlockEntityReusableBeehive beehive, float currentTemperature, double temperatureMultiplier)
|
||||||
|
{
|
||||||
|
if (beehive.BeePopulation <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
double beeFactor = beehive.BeePopulation / cfg.ReferenceBees;
|
||||||
|
double feed_consumption_speed = cfg.MaxFlowersForHoneyProduction / cfg.ReferenceFlowers * 1.5;
|
||||||
|
|
||||||
|
double feedTemperatureMultiplier = temperatureMultiplier;
|
||||||
|
if (cfg.WinterHardMode)
|
||||||
|
{
|
||||||
|
double winterReverseRamp = GetWinterReverseRamp(currentTemperature);
|
||||||
|
double winterFeedFloor = winterReverseRamp * cfg.WinterFoodConsumptionMultiplier;
|
||||||
|
feedTemperatureMultiplier = Math.Max(feedTemperatureMultiplier, winterFeedFloor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return beeFactor * feed_consumption_speed * feedTemperatureMultiplier;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double GetWinterReverseRamp(float currentTemperature)
|
||||||
|
{
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
if (currentTemperature >= cfg.MaxTemperatureGrowth)
|
||||||
|
return 0;
|
||||||
|
if (currentTemperature <= cfg.MinTemperatureGrowth)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
float temperatureRange = cfg.MaxTemperatureGrowth - cfg.MinTemperatureGrowth;
|
||||||
|
if (temperatureRange <= 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 1.0 - ((currentTemperature - cfg.MinTemperatureGrowth) / temperatureRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double GetEffectiveFlowers(BlockEntityReusableBeehive beehive)
|
||||||
|
{
|
||||||
|
const double cropMultiplier = 0.25;
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
if (beehive.FlowersAround is null || beehive.CropsAround is null)
|
||||||
|
return cfg.ReferenceFlowers;
|
||||||
|
double effectiveFlowers = beehive.FlowersAround.Value + (beehive.CropsAround.Value * cropMultiplier);
|
||||||
|
return Math.Min(effectiveFlowers, cfg.MaxFlowersForHoneyProduction);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct BeehiveStatsComponents(
|
||||||
|
int DailyDeaths,
|
||||||
|
double DailyGrowthRate,
|
||||||
|
int DailyPercentageGrowth,
|
||||||
|
int FilledFramesCount,
|
||||||
|
int EmptyFrames,
|
||||||
|
int TotalFrames,
|
||||||
|
int DailyFilledFrameBonus,
|
||||||
|
int DailyGrowth,
|
||||||
|
double TemperatureMultiplier,
|
||||||
|
double EffectiveFlowers,
|
||||||
|
bool IsGreenhouse,
|
||||||
|
float Temperature
|
||||||
|
);
|
||||||
652
OrekiWoofsBeehives/BlockEntities/BlockEntityBeeSwarm.cs
Normal file
652
OrekiWoofsBeehives/BlockEntities/BlockEntityBeeSwarm.cs
Normal file
@@ -0,0 +1,652 @@
|
|||||||
|
using OrekiWoofsBeehives.Utilities;
|
||||||
|
using OrekiWoofsBees.Common;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.Datastructures;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
using Vintagestory.API.Config;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives.BlockEntities;
|
||||||
|
|
||||||
|
public class BlockEntityBeeSwarm : BlockEntity
|
||||||
|
{
|
||||||
|
public double MigrationPhaseDurationHours { get; protected set; } = 5.0;
|
||||||
|
|
||||||
|
public BlockPos OriginHivePos { get; private set; } = new BlockPos(0);
|
||||||
|
public double Population { get; private set; }
|
||||||
|
public double SpawnDateTime { get; private set; }
|
||||||
|
public SwarmState SwarmState { get; private set; } = SwarmState.BuildingSwarm;
|
||||||
|
|
||||||
|
private double plannedPopulation;
|
||||||
|
private double stateStartTotalHours;
|
||||||
|
private double buildingDurationHours = 3.0;
|
||||||
|
private double hangingDurationHours = 5.0;
|
||||||
|
private double migrationStartPopulation;
|
||||||
|
private double transferredDuringMigration;
|
||||||
|
private int retryDay = -1;
|
||||||
|
private BlockPos? targetPos;
|
||||||
|
private bool targetIsVanillaSkep;
|
||||||
|
private string? targetPopulatedSkepCode;
|
||||||
|
private int eligibleTargetsAtLastSelection;
|
||||||
|
private List<SwarmTargetCandidate>? targetHiveCandidates;
|
||||||
|
private long? candidateRefreshListenerId;
|
||||||
|
|
||||||
|
public override void Initialize(ICoreAPI api)
|
||||||
|
{
|
||||||
|
base.Initialize(api);
|
||||||
|
#if DEBUG
|
||||||
|
RegisterGameTickListener(OnGameTick, 500);
|
||||||
|
#else
|
||||||
|
RegisterGameTickListener(OnGameTick, 5_000);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (api.Side != EnumAppSide.Server)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (SwarmState == SwarmState.HangingOut)
|
||||||
|
StartCandidateRefresh();
|
||||||
|
|
||||||
|
FastForwardTo(api.World.Calendar.TotalHours, debugAsCatchUp: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void InitializeFromOrigin(
|
||||||
|
BlockPos originHivePos,
|
||||||
|
double plannedPopulation,
|
||||||
|
double spawnTotalHours,
|
||||||
|
double buildingDurationHours,
|
||||||
|
double hangingDurationHours)
|
||||||
|
{
|
||||||
|
OriginHivePos = originHivePos.Copy();
|
||||||
|
this.plannedPopulation = Math.Max(0, plannedPopulation);
|
||||||
|
SpawnDateTime = spawnTotalHours;
|
||||||
|
stateStartTotalHours = spawnTotalHours;
|
||||||
|
this.buildingDurationHours = Math.Max(0.1, buildingDurationHours);
|
||||||
|
this.hangingDurationHours = Math.Max(0.1, hangingDurationHours);
|
||||||
|
SwarmState = SwarmState.BuildingSwarm;
|
||||||
|
Population = 0;
|
||||||
|
retryDay = -1;
|
||||||
|
targetPos = null;
|
||||||
|
targetIsVanillaSkep = false;
|
||||||
|
targetPopulatedSkepCode = null;
|
||||||
|
eligibleTargetsAtLastSelection = 0;
|
||||||
|
targetHiveCandidates = null;
|
||||||
|
migrationStartPopulation = 0;
|
||||||
|
transferredDuringMigration = 0;
|
||||||
|
MarkDirty(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGameTick(float dt)
|
||||||
|
{
|
||||||
|
if (Api.Side != EnumAppSide.Server)
|
||||||
|
return;
|
||||||
|
|
||||||
|
FastForwardTo(Api.World.Calendar.TotalHours, debugAsCatchUp: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCandidateRefreshTick(float dt)
|
||||||
|
{
|
||||||
|
if (Api.Side != EnumAppSide.Server || SwarmState != SwarmState.HangingOut)
|
||||||
|
return;
|
||||||
|
|
||||||
|
targetHiveCandidates = FindEligibleTargets();
|
||||||
|
eligibleTargetsAtLastSelection = targetHiveCandidates.Count;
|
||||||
|
MarkDirty(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartCandidateRefresh()
|
||||||
|
{
|
||||||
|
StopCandidateRefresh();
|
||||||
|
candidateRefreshListenerId = RegisterGameTickListener(OnCandidateRefreshTick, 5_000);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StopCandidateRefresh()
|
||||||
|
{
|
||||||
|
if (candidateRefreshListenerId.HasValue)
|
||||||
|
{
|
||||||
|
UnregisterGameTickListener(candidateRefreshListenerId.Value);
|
||||||
|
candidateRefreshListenerId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FastForwardTo(double nowHours)
|
||||||
|
{
|
||||||
|
FastForwardTo(nowHours, debugAsCatchUp: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FastForwardTo(double nowHours, bool debugAsCatchUp)
|
||||||
|
{
|
||||||
|
if (Api?.Side != EnumAppSide.Server)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (nowHours <= SpawnDateTime)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var debugEnabled = debugAsCatchUp && Api.GetOrekiWoofsBeehives()?.DebugUnloadEnabled == true;
|
||||||
|
var initialState = SwarmState;
|
||||||
|
var initialPopulation = Population;
|
||||||
|
var initialTargetPos = targetPos?.Copy();
|
||||||
|
var initialTransferred = transferredDuringMigration;
|
||||||
|
|
||||||
|
if (debugEnabled)
|
||||||
|
BroadcastUnloadDebug($"swarm {Pos}: catch-up start from state={initialState}, population={initialPopulation:F2}, spawnHour={SpawnDateTime:F2}, targetHour={nowHours:F2}");
|
||||||
|
|
||||||
|
for (int i = 0; i < 12; i++)
|
||||||
|
{
|
||||||
|
if (Api.World.BlockAccessor.GetBlockEntity(Pos) is not BlockEntityBeeSwarm)
|
||||||
|
{
|
||||||
|
if (debugEnabled)
|
||||||
|
BroadcastUnloadDebug($"swarm {Pos}: removed during catch-up");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var previousState = SwarmState;
|
||||||
|
var previousPopulation = Population;
|
||||||
|
var previousStateStartTotalHours = stateStartTotalHours;
|
||||||
|
var previousRetryDay = retryDay;
|
||||||
|
var previousTransferredDuringMigration = transferredDuringMigration;
|
||||||
|
var previousTargetPos = targetPos?.Copy();
|
||||||
|
|
||||||
|
switch (SwarmState)
|
||||||
|
{
|
||||||
|
case SwarmState.BuildingSwarm:
|
||||||
|
UpdateBuildingSwarm(nowHours);
|
||||||
|
break;
|
||||||
|
case SwarmState.HangingOut:
|
||||||
|
UpdateHangingOut(nowHours);
|
||||||
|
break;
|
||||||
|
case SwarmState.MigratingToNewHive:
|
||||||
|
UpdateMigratingToNewHive(nowHours);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Api.World.BlockAccessor.GetBlockEntity(Pos) is not BlockEntityBeeSwarm)
|
||||||
|
{
|
||||||
|
if (debugEnabled)
|
||||||
|
BroadcastUnloadDebug($"swarm {Pos}: finished and removed during catch-up");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var changed =
|
||||||
|
previousState != SwarmState ||
|
||||||
|
Math.Abs(previousPopulation - Population) > 0.0001 ||
|
||||||
|
previousStateStartTotalHours != stateStartTotalHours ||
|
||||||
|
previousRetryDay != retryDay ||
|
||||||
|
Math.Abs(previousTransferredDuringMigration - transferredDuringMigration) > 0.0001 ||
|
||||||
|
!Equals(previousTargetPos, targetPos);
|
||||||
|
|
||||||
|
if (debugEnabled)
|
||||||
|
{
|
||||||
|
if (previousState != SwarmState)
|
||||||
|
BroadcastUnloadDebug($"swarm {Pos}: state {previousState} -> {SwarmState} at hour={nowHours:F2}");
|
||||||
|
|
||||||
|
var movedNow = transferredDuringMigration - previousTransferredDuringMigration;
|
||||||
|
if (movedNow > 0.0001)
|
||||||
|
BroadcastUnloadDebug($"swarm {Pos}: moved {movedNow:F2} bees this catch-up step (remaining={Population:F2})");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changed)
|
||||||
|
{
|
||||||
|
if (debugEnabled)
|
||||||
|
BroadcastUnloadDebug($"swarm {Pos}: catch-up reached stable state={SwarmState}, population={Population:F2}, transferred={transferredDuringMigration:F2}");
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (debugEnabled)
|
||||||
|
{
|
||||||
|
BroadcastUnloadDebug(
|
||||||
|
$"swarm {Pos}: catch-up loop limit reached; state {initialState}->{SwarmState}, population {initialPopulation:F2}->{Population:F2}, transferred {initialTransferred:F2}->{transferredDuringMigration:F2}, target {initialTargetPos}->{targetPos}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateBuildingSwarm(double nowHours)
|
||||||
|
{
|
||||||
|
var elapsedHours = Math.Max(0, nowHours - stateStartTotalHours);
|
||||||
|
var progress = Math.Clamp(elapsedHours / buildingDurationHours, 0, 1);
|
||||||
|
var desiredPopulationInSwarm = plannedPopulation * progress;
|
||||||
|
var toMoveNow = desiredPopulationInSwarm - Population;
|
||||||
|
|
||||||
|
if (toMoveNow > 0)
|
||||||
|
{
|
||||||
|
var sourceHive = GetOriginHive();
|
||||||
|
if (sourceHive != null)
|
||||||
|
{
|
||||||
|
var moved = sourceHive.TakeBeePopulationForSwarm(toMoveNow);
|
||||||
|
Population += moved;
|
||||||
|
MarkDirty(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SwarmState = SwarmState.HangingOut;
|
||||||
|
stateStartTotalHours = nowHours;
|
||||||
|
StartCandidateRefresh();
|
||||||
|
MarkDirty(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateHangingOut(double nowHours)
|
||||||
|
{
|
||||||
|
if (retryDay >= 0)
|
||||||
|
{
|
||||||
|
if (CanRetryInCurrentWindow(nowHours))
|
||||||
|
TryStartMigration(nowHours);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var elapsedHours = Math.Max(0, nowHours - stateStartTotalHours);
|
||||||
|
if (elapsedHours < hangingDurationHours)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TryStartMigration(nowHours);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CanRetryInCurrentWindow(double nowHours)
|
||||||
|
{
|
||||||
|
var currentDay = GetCurrentDayIndex(nowHours);
|
||||||
|
if (currentDay < retryDay)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var hourOfDay = Api.World.Calendar.HourOfDay;
|
||||||
|
return hourOfDay is >= 8 and <= 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryStartMigration(double nowHours)
|
||||||
|
{
|
||||||
|
targetHiveCandidates = FindEligibleTargets();
|
||||||
|
eligibleTargetsAtLastSelection = targetHiveCandidates.Count;
|
||||||
|
|
||||||
|
if (targetHiveCandidates.Count == 0)
|
||||||
|
{
|
||||||
|
var currentDay = GetCurrentDayIndex(nowHours);
|
||||||
|
if (retryDay < 0)
|
||||||
|
{
|
||||||
|
retryDay = currentDay + 1;
|
||||||
|
MarkDirty(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Disperse();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var candidate in targetHiveCandidates)
|
||||||
|
{
|
||||||
|
if (candidate.IsVanillaSkep)
|
||||||
|
{
|
||||||
|
if (TryConvertSkepToPopulated(candidate.Pos, candidate.PopulatedSkepCode!))
|
||||||
|
{
|
||||||
|
StartMigrating(nowHours, candidate.Pos, true, candidate.PopulatedSkepCode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
StartMigrating(nowHours, candidate.Pos, false, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Disperse();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartMigrating(double nowHours, BlockPos targetPos, bool targetIsVanillaSkep, string? targetPopulatedSkepCode)
|
||||||
|
{
|
||||||
|
StopCandidateRefresh();
|
||||||
|
SwarmState = SwarmState.MigratingToNewHive;
|
||||||
|
stateStartTotalHours = nowHours;
|
||||||
|
migrationStartPopulation = Population;
|
||||||
|
transferredDuringMigration = 0;
|
||||||
|
this.targetPos = targetPos.Copy();
|
||||||
|
this.targetIsVanillaSkep = targetIsVanillaSkep;
|
||||||
|
this.targetPopulatedSkepCode = targetPopulatedSkepCode;
|
||||||
|
retryDay = -1;
|
||||||
|
|
||||||
|
if (!targetIsVanillaSkep && Api.World.BlockAccessor.GetBlockEntity(targetPos) is BlockEntityReusableBeehive targetHive)
|
||||||
|
targetHive.SetIncomingSwarm(Pos);
|
||||||
|
|
||||||
|
MarkDirty(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateMigratingToNewHive(double nowHours)
|
||||||
|
{
|
||||||
|
if (targetPos == null)
|
||||||
|
{
|
||||||
|
Disperse();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var elapsedHours = Math.Max(0, nowHours - stateStartTotalHours);
|
||||||
|
var progress = Math.Clamp(elapsedHours / MigrationPhaseDurationHours, 0, 1);
|
||||||
|
var desiredTransferred = migrationStartPopulation * progress;
|
||||||
|
var toTransferNow = desiredTransferred - transferredDuringMigration;
|
||||||
|
|
||||||
|
if (toTransferNow > 0)
|
||||||
|
{
|
||||||
|
var moved = targetIsVanillaSkep
|
||||||
|
? Math.Min(Population, Math.Max(0, toTransferNow))
|
||||||
|
: MoveToTargetHive(toTransferNow);
|
||||||
|
|
||||||
|
transferredDuringMigration += moved;
|
||||||
|
Population = Math.Max(0, Population - moved);
|
||||||
|
MarkDirty(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (progress < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
RemoveSwarmBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private double MoveToTargetHive(double amount)
|
||||||
|
{
|
||||||
|
if (targetPos == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (Api.World.BlockAccessor.GetBlockEntity(targetPos) is not BlockEntityReusableBeehive targetHive)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return targetHive.AddBeePopulationFromSwarm(amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SwarmTargetCandidate> FindEligibleTargets()
|
||||||
|
{
|
||||||
|
var candidates = new List<SwarmTargetCandidate>();
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
var radius = Math.Max(1, cfg.BeehiveRadius);
|
||||||
|
|
||||||
|
var modSystem = Api.GetOrekiWoofsBeehives();
|
||||||
|
if (modSystem != null)
|
||||||
|
{
|
||||||
|
foreach (var pos in modSystem.BeehiveRegistry.BeehivePositions)
|
||||||
|
{
|
||||||
|
var targetPos = new BlockPos(pos.X, pos.Y, pos.Z);
|
||||||
|
if (targetPos.Equals(OriginHivePos) || targetPos.Equals(Pos))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!Overlaps.IsWithinSphericalRadius(Pos, pos, radius))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (Api.World.BlockAccessor.GetBlockEntity(targetPos) is not BlockEntityReusableBeehive hive)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!hive.OpenForIncomingSwarms)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var score = CalculateBeehiveTargetScore(hive, targetPos, radius);
|
||||||
|
candidates.Add(new SwarmTargetCandidate(targetPos, false, score, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var skepRegistry = modSystem?.VanillaSkepRegistry;
|
||||||
|
if (skepRegistry != null)
|
||||||
|
{
|
||||||
|
foreach (var entry in skepRegistry.Entries)
|
||||||
|
{
|
||||||
|
var targetPos = new BlockPos(entry.Key.X, entry.Key.Y, entry.Key.Z);
|
||||||
|
if (!Overlaps.IsWithinSphericalRadius(Pos, entry.Key, radius))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (targetPos.Equals(OriginHivePos) || targetPos.Equals(Pos))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var block = Api.World.BlockAccessor.GetBlock(targetPos);
|
||||||
|
if (block?.Code?.Path?.Contains("empty") != true)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var score = CalculateSkepTargetScore(targetPos, radius);
|
||||||
|
candidates.Add(new SwarmTargetCandidate(targetPos, true, score, entry.Value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
candidates.Sort((a, b) => b.Score.CompareTo(a.Score));
|
||||||
|
return candidates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryConvertSkepToPopulated(BlockPos pos, string populatedCode)
|
||||||
|
{
|
||||||
|
var block = Api.World.GetBlock(new AssetLocation(populatedCode));
|
||||||
|
if (block == null || block.Id == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Api.World.BlockAccessor.ExchangeBlock(block.BlockId, pos);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double CalculateBeehiveTargetScore(BlockEntityReusableBeehive hive, BlockPos targetPos, int radius)
|
||||||
|
{
|
||||||
|
var distanceFactor = GetDistanceFactor(targetPos, radius);
|
||||||
|
var flowerFactor = GetBeehiveFlowerFactor(hive);
|
||||||
|
var frameFactor = hive.CountFilledFrames() > 0 ? 2.0 : 1.0;
|
||||||
|
return distanceFactor * flowerFactor * frameFactor + 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double GetBeehiveFlowerFactor(BlockEntityReusableBeehive hive)
|
||||||
|
{
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
var maxFlowers = Math.Max(1, cfg.MaxFlowersForHoneyProduction);
|
||||||
|
var flowers = hive.FlowersAround.GetValueOrDefault(0);
|
||||||
|
var crops = hive.CropsAround.GetValueOrDefault(0);
|
||||||
|
var effectiveFlowers = Math.Min(maxFlowers, flowers + crops * 0.25);
|
||||||
|
var normalized = effectiveFlowers / maxFlowers;
|
||||||
|
return Math.Max(0.05, normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double CalculateSkepTargetScore(BlockPos targetPos, int radius)
|
||||||
|
{
|
||||||
|
var distanceFactor = GetDistanceFactor(targetPos, radius);
|
||||||
|
var flowerFactor = GetSkepFlowerFactor(targetPos);
|
||||||
|
return distanceFactor * flowerFactor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double GetSkepFlowerFactor(BlockPos targetPos)
|
||||||
|
{
|
||||||
|
var registry = Api.GetPlantPositionRegistry();
|
||||||
|
if (registry == null)
|
||||||
|
return 0.05;
|
||||||
|
|
||||||
|
var (flowers, crops, _, _) = registry.GetPlantCountsNearPosition(targetPos, Config.Instance.BeehiveRadius);
|
||||||
|
var maxFlowers = Math.Max(1, Config.Instance.MaxFlowersForHoneyProduction);
|
||||||
|
var effectiveFlowers = Math.Min(maxFlowers, flowers + crops * 0.25);
|
||||||
|
var normalized = effectiveFlowers / maxFlowers;
|
||||||
|
return Math.Max(0.05, normalized);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double GetDistanceFactor(BlockPos targetPos, int radius)
|
||||||
|
{
|
||||||
|
var distance = targetPos.DistanceTo(Pos);
|
||||||
|
var normalized = Math.Clamp(1.0 - (distance / radius), 0.01, 1.0);
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void GetBlockInfo(IPlayer forPlayer, StringBuilder dsc)
|
||||||
|
{
|
||||||
|
dsc.AppendLine(GetPhaseStatusLine());
|
||||||
|
dsc.AppendLine(Lang.Get("orekiwoofsbeehives:beeswarm-info-population", Population.ToString("N0")));
|
||||||
|
|
||||||
|
var eligibleTargets = targetHiveCandidates?.Count ?? eligibleTargetsAtLastSelection;
|
||||||
|
dsc.AppendLine(Lang.Get("orekiwoofsbeehives:beeswarm-info-eligible-targets", eligibleTargets));
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetPhaseStatusLine()
|
||||||
|
{
|
||||||
|
var hoursLeft = GetHoursLeftInCurrentPhase();
|
||||||
|
var hoursLeftText = hoursLeft < 1
|
||||||
|
? Lang.Get("orekiwoofsbeehives:beeswarm-less-than-hour-left")
|
||||||
|
: Lang.Get("orekiwoofsbeehives:beeswarm-hours-left", Math.Ceiling(hoursLeft).ToString("F0"));
|
||||||
|
|
||||||
|
return SwarmState switch
|
||||||
|
{
|
||||||
|
SwarmState.BuildingSwarm => Lang.Get("orekiwoofsbeehives:beeswarm-phase-forming", hoursLeftText),
|
||||||
|
SwarmState.HangingOut => Lang.Get("orekiwoofsbeehives:beeswarm-phase-scouting", hoursLeftText),
|
||||||
|
SwarmState.MigratingToNewHive => Lang.Get("orekiwoofsbeehives:beeswarm-phase-moving", hoursLeftText),
|
||||||
|
_ => Lang.Get("orekiwoofsbeehives:beeswarm-phase-scouting", hoursLeftText),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private double GetHoursLeftInCurrentPhase()
|
||||||
|
{
|
||||||
|
var now = Api.World.Calendar.TotalHours;
|
||||||
|
|
||||||
|
if (SwarmState == SwarmState.BuildingSwarm)
|
||||||
|
return Math.Max(0, buildingDurationHours - (now - stateStartTotalHours));
|
||||||
|
|
||||||
|
if (SwarmState == SwarmState.HangingOut)
|
||||||
|
{
|
||||||
|
if (retryDay >= 0)
|
||||||
|
{
|
||||||
|
var nextPickHour = retryDay * Api.World.Calendar.HoursPerDay + 8;
|
||||||
|
return Math.Max(0, nextPickHour - now);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.Max(0, hangingDurationHours - (now - stateStartTotalHours));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SwarmState == SwarmState.MigratingToNewHive)
|
||||||
|
return Math.Max(0, MigrationPhaseDurationHours - (now - stateStartTotalHours));
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetCurrentDayIndex(double nowHours)
|
||||||
|
{
|
||||||
|
var hpd = Math.Max(1, Api.World.Calendar.HoursPerDay);
|
||||||
|
return (int)Math.Floor(nowHours / hpd);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockEntityReusableBeehive? GetOriginHive()
|
||||||
|
{
|
||||||
|
return Api.World.BlockAccessor.GetBlockEntity(OriginHivePos) as BlockEntityReusableBeehive;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Disperse()
|
||||||
|
{
|
||||||
|
StopCandidateRefresh();
|
||||||
|
var originHive = GetOriginHive();
|
||||||
|
if (originHive != null && Population > 0)
|
||||||
|
{
|
||||||
|
var returnPercent = Math.Clamp(Config.Instance.SwarmReturnToOriginOnFailedMigrationPercent, 0, 100);
|
||||||
|
if (returnPercent > 0)
|
||||||
|
{
|
||||||
|
var returnAmount = Population * returnPercent / 100.0;
|
||||||
|
originHive.AddBeePopulationFromSwarm(returnAmount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveSwarmBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RemoveSwarmBlock()
|
||||||
|
{
|
||||||
|
NotifyTargetHiveSwarmFinished();
|
||||||
|
NotifyOriginHiveSwarmFinished();
|
||||||
|
Api.World.BlockAccessor.SetBlock(0, Pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NotifyOriginHiveSwarmFinished()
|
||||||
|
{
|
||||||
|
var originHive = GetOriginHive();
|
||||||
|
originHive?.ClearActiveSwarm(Pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NotifyTargetHiveSwarmFinished()
|
||||||
|
{
|
||||||
|
if (targetIsVanillaSkep || targetPos == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (Api.World.BlockAccessor.GetBlockEntity(targetPos) is BlockEntityReusableBeehive targetHive)
|
||||||
|
targetHive.ClearIncomingSwarm(Pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBlockRemoved()
|
||||||
|
{
|
||||||
|
NotifyTargetHiveSwarmFinished();
|
||||||
|
NotifyOriginHiveSwarmFinished();
|
||||||
|
base.OnBlockRemoved();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ToTreeAttributes(ITreeAttribute tree)
|
||||||
|
{
|
||||||
|
base.ToTreeAttributes(tree);
|
||||||
|
|
||||||
|
tree.SetBlockPos("originPos", OriginHivePos);
|
||||||
|
tree.SetDouble("population", Population);
|
||||||
|
tree.SetDouble("plannedPopulation", plannedPopulation);
|
||||||
|
tree.SetDouble("spawnDateTime", SpawnDateTime);
|
||||||
|
tree.SetDouble("stateStartTotalHours", stateStartTotalHours);
|
||||||
|
tree.SetDouble(nameof(MigrationPhaseDurationHours), MigrationPhaseDurationHours);
|
||||||
|
tree.SetDouble("buildingDurationHours", buildingDurationHours);
|
||||||
|
tree.SetDouble("hangingDurationHours", hangingDurationHours);
|
||||||
|
tree.SetInt("swarmState", (int)SwarmState);
|
||||||
|
tree.SetDouble("migrationStartPopulation", migrationStartPopulation);
|
||||||
|
tree.SetDouble("transferredDuringMigration", transferredDuringMigration);
|
||||||
|
tree.SetInt("retryDay", retryDay);
|
||||||
|
tree.SetBool("targetIsVanillaSkep", targetIsVanillaSkep);
|
||||||
|
tree.SetString("targetPopulatedSkepCode", targetPopulatedSkepCode ?? string.Empty);
|
||||||
|
tree.SetInt("eligibleTargetsAtLastSelection", eligibleTargetsAtLastSelection);
|
||||||
|
|
||||||
|
// for roamingbees
|
||||||
|
tree.SetInt("roamingbees_swarm_state", (int)SwarmState);
|
||||||
|
tree.SetBlockPos("roamingbees_swarm_originHivePos", OriginHivePos);
|
||||||
|
if (targetPos is not null)
|
||||||
|
tree.SetBlockPos("roamingbees_swarm_targetHivePos", targetPos);
|
||||||
|
|
||||||
|
tree["roamingbees_swarm_candidateHives"] = BuildCandidateHivesTreeArray();
|
||||||
|
|
||||||
|
if (targetPos != null)
|
||||||
|
tree.SetBlockPos("targetPos", targetPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TreeArrayAttribute BuildCandidateHivesTreeArray()
|
||||||
|
{
|
||||||
|
if (targetHiveCandidates == null)
|
||||||
|
return new TreeArrayAttribute([]);
|
||||||
|
|
||||||
|
var count = Math.Min(targetHiveCandidates.Count, 10);
|
||||||
|
var entries = new TreeAttribute[count];
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var entry = new TreeAttribute();
|
||||||
|
entry.SetBlockPos("pos", targetHiveCandidates[i].Pos);
|
||||||
|
entries[i] = entry;
|
||||||
|
}
|
||||||
|
return new TreeArrayAttribute(entries);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FromTreeAttributes(ITreeAttribute tree, IWorldAccessor worldAccessForResolve)
|
||||||
|
{
|
||||||
|
base.FromTreeAttributes(tree, worldAccessForResolve);
|
||||||
|
|
||||||
|
OriginHivePos = tree.GetBlockPos("originPos");
|
||||||
|
Population = tree.GetDouble("population");
|
||||||
|
plannedPopulation = tree.GetDouble("plannedPopulation");
|
||||||
|
SpawnDateTime = tree.GetDouble("spawnDateTime");
|
||||||
|
stateStartTotalHours = tree.GetDouble("stateStartTotalHours");
|
||||||
|
if (tree.HasAttribute(nameof(MigrationPhaseDurationHours)))
|
||||||
|
MigrationPhaseDurationHours = tree.GetDouble(nameof(MigrationPhaseDurationHours));
|
||||||
|
buildingDurationHours = tree.GetDouble("buildingDurationHours");
|
||||||
|
hangingDurationHours = tree.GetDouble("hangingDurationHours");
|
||||||
|
SwarmState = (SwarmState)tree.GetInt("swarmState");
|
||||||
|
migrationStartPopulation = tree.GetDouble("migrationStartPopulation");
|
||||||
|
transferredDuringMigration = tree.GetDouble("transferredDuringMigration");
|
||||||
|
retryDay = tree.GetInt("retryDay");
|
||||||
|
targetIsVanillaSkep = tree.GetBool("targetIsVanillaSkep");
|
||||||
|
targetPopulatedSkepCode = tree.GetString("targetPopulatedSkepCode");
|
||||||
|
eligibleTargetsAtLastSelection = tree.GetInt("eligibleTargetsAtLastSelection");
|
||||||
|
|
||||||
|
targetPos = tree.GetBlockPos("targetPos");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BroadcastUnloadDebug(string message)
|
||||||
|
{
|
||||||
|
Api.GetOrekiWoofsBeehives()?.BroadcastUnloadDebug(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly record struct SwarmTargetCandidate(BlockPos Pos, bool IsVanillaSkep, double Score, string? PopulatedSkepCode);
|
||||||
|
}
|
||||||
1062
OrekiWoofsBeehives/BlockEntities/BlockEntityReusableBeehive.cs
Normal file
1062
OrekiWoofsBeehives/BlockEntities/BlockEntityReusableBeehive.cs
Normal file
File diff suppressed because it is too large
Load Diff
55
OrekiWoofsBeehives/Blocks/BlockBeeSwarm.cs
Normal file
55
OrekiWoofsBeehives/Blocks/BlockBeeSwarm.cs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
using OrekiWoofsBeehives.BlockEntities;
|
||||||
|
using System;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives.Blocks;
|
||||||
|
|
||||||
|
public class BlockBeeSwarm : Block
|
||||||
|
{
|
||||||
|
private static readonly Cuboidf northBox = new(0.25f, 0.25f, 0f, 0.75f, 0.75f, 0.2f);
|
||||||
|
private static readonly Cuboidf eastBox = new(0.8f, 0.25f, 0.25f, 1f, 0.75f, 0.75f);
|
||||||
|
private static readonly Cuboidf southBox = new(0.25f, 0.25f, 0.8f, 0.75f, 0.75f, 1f);
|
||||||
|
private static readonly Cuboidf westBox = new(0f, 0.25f, 0.25f, 0.2f, 0.75f, 0.75f);
|
||||||
|
private static readonly Cuboidf downBox = new(0.25f, 0f, 0.25f, 0.75f, 0.2f, 0.75f);
|
||||||
|
private static readonly Cuboidf upBox = new(0.25f, 0.8f, 0.25f, 0.75f, 1f, 0.75f);
|
||||||
|
|
||||||
|
public override float GetAmbientSoundStrength(IWorldAccessor world, BlockPos pos)
|
||||||
|
{
|
||||||
|
if (world.BlockAccessor.GetBlockEntity(pos) is not BlockEntityBeeSwarm swarm)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (swarm.Population < 100)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (swarm.Population < Config.Instance.BeehiveConsideredEmptyBelowPopulation)
|
||||||
|
return Math.Clamp(Config.Instance.BeehiveAmbientVolume * 0.2f, 0f, 1f);
|
||||||
|
|
||||||
|
return Math.Clamp(Config.Instance.BeehiveAmbientVolume, 0f, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Cuboidf[] GetSelectionBoxes(IBlockAccessor blockAccessor, BlockPos pos)
|
||||||
|
{
|
||||||
|
return [GetCurrentVariantBox().Clone()];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Cuboidf[] GetCollisionBoxes(IBlockAccessor blockAccessor, BlockPos pos)
|
||||||
|
{
|
||||||
|
return [GetCurrentVariantBox().Clone()];
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cuboidf GetCurrentVariantBox()
|
||||||
|
{
|
||||||
|
var side = Variant?["side"] ?? "north";
|
||||||
|
return side switch
|
||||||
|
{
|
||||||
|
"north" => northBox,
|
||||||
|
"east" => eastBox,
|
||||||
|
"south" => southBox,
|
||||||
|
"west" => westBox,
|
||||||
|
"down" => downBox,
|
||||||
|
"up" => upBox,
|
||||||
|
_ => northBox,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
51
OrekiWoofsBeehives/Blocks/BlockBeehiveFrame.cs
Normal file
51
OrekiWoofsBeehives/Blocks/BlockBeehiveFrame.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.Config;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives.Blocks;
|
||||||
|
|
||||||
|
public class BlockBeehiveFrame : Block
|
||||||
|
{
|
||||||
|
private const string feed_remaining_attribute = "feedRemaining";
|
||||||
|
private const double feed_full_amount = 1.0;
|
||||||
|
|
||||||
|
public override void GetHeldItemInfo(ItemSlot inSlot, StringBuilder dsc, IWorldAccessor world, bool withDebugInfo)
|
||||||
|
{
|
||||||
|
base.GetHeldItemInfo(inSlot, dsc, world, withDebugInfo);
|
||||||
|
|
||||||
|
var stack = inSlot.Itemstack;
|
||||||
|
if (stack?.Block?.Code?.Path != "beehiveframe-filled-feed")
|
||||||
|
return;
|
||||||
|
|
||||||
|
var remaining = GetFeedRemaining(stack);
|
||||||
|
var remainingPercent = Math.Clamp(remaining * 100.0, 0, 100);
|
||||||
|
if (remainingPercent >= 100)
|
||||||
|
return;
|
||||||
|
|
||||||
|
dsc.AppendLine(FormatLang("orekiwoofsbeehives:beehiveframe-feed-percent", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["percent"] = $"{remainingPercent:F0}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double GetFeedRemaining(ItemStack stack)
|
||||||
|
{
|
||||||
|
if (stack.Attributes == null)
|
||||||
|
return feed_full_amount;
|
||||||
|
|
||||||
|
if (!stack.Attributes.HasAttribute(feed_remaining_attribute))
|
||||||
|
return feed_full_amount;
|
||||||
|
|
||||||
|
return Math.Clamp(stack.Attributes.GetDouble(feed_remaining_attribute), 0, feed_full_amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatLang(string langKey, Dictionary<string, string> values)
|
||||||
|
{
|
||||||
|
var result = Lang.Get(langKey);
|
||||||
|
foreach (var kvp in values)
|
||||||
|
result = result.Replace($">>>{kvp.Key}<<<", kvp.Value);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
678
OrekiWoofsBeehives/Blocks/BlockReusableBeehive.cs
Normal file
678
OrekiWoofsBeehives/Blocks/BlockReusableBeehive.cs
Normal file
@@ -0,0 +1,678 @@
|
|||||||
|
using OrekiWoofsBeehives.BlockEntities;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using Vintagestory.API.Client;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.Config;
|
||||||
|
using Vintagestory.API.Datastructures;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
using Vintagestory.API.Util;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives.Blocks;
|
||||||
|
|
||||||
|
public class BlockReusableBeehive : Block
|
||||||
|
{
|
||||||
|
private readonly WorldInteraction openCloseInteraction = new() { ActionLangCode = "blockhelp-door-openclose", MouseButton = EnumMouseButton.Right };
|
||||||
|
private readonly AssetLocation beehiveFrameEmptyAsset = new("orekiwoofsbeehives:beehiveframe-empty");
|
||||||
|
private readonly AssetLocation beehiveFrameFilledAsset = new("orekiwoofsbeehives:beehiveframe-filled");
|
||||||
|
private readonly AssetLocation beehiveFrameFilledFeedAsset = new("orekiwoofsbeehives:beehiveframe-filled-feed");
|
||||||
|
|
||||||
|
private readonly Cuboidf[] slotBoxes = new Cuboidf[8];
|
||||||
|
private readonly Cuboidf[] closedSelectionBoxes = [];
|
||||||
|
private readonly Cuboidf[] openSelectionBoxes = [];
|
||||||
|
|
||||||
|
private MeshData? closedMesh;
|
||||||
|
private MeshData? openMesh;
|
||||||
|
|
||||||
|
public BlockReusableBeehive()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
float x1 = 2 + i * 1.5f;
|
||||||
|
float x2 = x1 + 1.5f;
|
||||||
|
slotBoxes[i] = new Cuboidf(
|
||||||
|
x1 / 16f, 6 / 16f, 2 / 16f,
|
||||||
|
x2 / 16f, 14 / 16f, 14 / 16f
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
closedSelectionBoxes =
|
||||||
|
[
|
||||||
|
new Cuboidf(0, 0, 0, 1, 0.375f, 1),
|
||||||
|
new Cuboidf(0, 0.875f, 0, 1, 1, 1),
|
||||||
|
new Cuboidf(0, 0.375f, 0.875f, 1, 0.875f, 1),
|
||||||
|
new Cuboidf(0, 0.375f, 0, 0.125f, 0.875f, 0.875f),
|
||||||
|
new Cuboidf(0.875f, 0.375f, 0, 1, 0.875f, 0.875f),
|
||||||
|
new Cuboidf(0.125f, 0.375f, 0, 0.875f, 0.875f, 0.125f)
|
||||||
|
];
|
||||||
|
|
||||||
|
openSelectionBoxes =
|
||||||
|
[
|
||||||
|
new Cuboidf(0, 0, 0, 1, 0.375f, 1),
|
||||||
|
new Cuboidf(0, 0.875f, 0, 1, 1, 1),
|
||||||
|
new Cuboidf(0, 0.375f, 0.875f, 1, 0.875f, 1),
|
||||||
|
new Cuboidf(0, 0.375f, 0, 0.125f, 0.875f, 0.875f),
|
||||||
|
new Cuboidf(0.875f, 0.375f, 0, 1, 0.875f, 0.875f)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override float GetAmbientSoundStrength(IWorldAccessor world, BlockPos pos)
|
||||||
|
{
|
||||||
|
var entity = GetBlockEntity<BlockEntityReusableBeehive>(pos);
|
||||||
|
if (entity == null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (entity.BeePopulation < 100)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (entity.BeePopulation < Config.Instance.BeehiveConsideredEmptyBelowPopulation)
|
||||||
|
return Math.Clamp(Config.Instance.BeehiveAmbientVolume * 0.2f, 0f, 1f);
|
||||||
|
|
||||||
|
return Math.Clamp(Config.Instance.BeehiveAmbientVolume, 0f, 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnLoaded(ICoreAPI api)
|
||||||
|
{
|
||||||
|
base.OnLoaded(api);
|
||||||
|
|
||||||
|
var frontFacing = GetFrontFacing();
|
||||||
|
if (frontFacing != null)
|
||||||
|
SideSolid[frontFacing.Index] = false;
|
||||||
|
|
||||||
|
if (api.Side != EnumAppSide.Client)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ICoreClientAPI capi = (api as ICoreClientAPI)!;
|
||||||
|
|
||||||
|
var closedShape = api.Assets.Get(new AssetLocation("orekiwoofsbeehives:shapes/block/beehive-closed.json")).ToObject<Shape>();
|
||||||
|
var openShape = api.Assets.Get(new AssetLocation("orekiwoofsbeehives:shapes/block/beehive-open.json")).ToObject<Shape>();
|
||||||
|
|
||||||
|
capi.Tesselator.TesselateShape(this, closedShape, out closedMesh);
|
||||||
|
capi.Tesselator.TesselateShape(this, openShape, out openMesh);
|
||||||
|
|
||||||
|
var rotateY = -GetRotationY();
|
||||||
|
|
||||||
|
closedMesh?.Rotate(new Vec3f(0.5f, 0.5f, 0.5f), 0, rotateY, 0);
|
||||||
|
openMesh?.Rotate(new Vec3f(0.5f, 0.5f, 0.5f), 0, rotateY, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBlockBroken(IWorldAccessor world, BlockPos pos, IPlayer? byPlayer, float dropQuantityMultiplier = 1f)
|
||||||
|
{
|
||||||
|
if (world.BlockAccessor.GetBlockEntity(pos) is not BlockEntityReusableBeehive be)
|
||||||
|
{
|
||||||
|
base.OnBlockBroken(world, pos, byPlayer, dropQuantityMultiplier);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (be.BeePopulation <= 0 && be.HoneyProgress <= 0 && !be.Inventory.Any(slot => !slot.Empty))
|
||||||
|
{
|
||||||
|
base.OnBlockBroken(world, pos, byPlayer, dropQuantityMultiplier);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var stack = new ItemStack(this);
|
||||||
|
var beehiveData = new TreeAttribute();
|
||||||
|
|
||||||
|
beehiveData.SetDouble("beePopulation", be.BeePopulation);
|
||||||
|
beehiveData.SetDouble("honeyProgress", be.HoneyProgress);
|
||||||
|
beehiveData.SetDouble("nextSwarmAllowedTotalDays", be.NextSwarmAllowedTotalDays);
|
||||||
|
beehiveData.SetDouble("preSwarmProgress", be.PreSwarmProgress);
|
||||||
|
beehiveData.SetBool("swarmsDisabled", be.SwarmsDisabled);
|
||||||
|
|
||||||
|
var inventoryTree = new TreeAttribute();
|
||||||
|
be.Inventory.ToTreeAttributes(inventoryTree);
|
||||||
|
beehiveData["inventory"] = inventoryTree;
|
||||||
|
|
||||||
|
stack.Attributes["beehiveData"] = beehiveData;
|
||||||
|
|
||||||
|
if (byPlayer == null)
|
||||||
|
be.OnBlockRemoved();
|
||||||
|
|
||||||
|
world.SpawnItemEntity(stack, pos.ToVec3d().Add(0.5, 0.5, 0.5));
|
||||||
|
world.BlockAccessor.SetBlock(0, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBlockPlaced(IWorldAccessor world, BlockPos blockPos, ItemStack? byItemStack = null)
|
||||||
|
{
|
||||||
|
base.OnBlockPlaced(world, blockPos, byItemStack);
|
||||||
|
|
||||||
|
if (byItemStack?.Attributes?.HasAttribute("beehiveData") != true)
|
||||||
|
{
|
||||||
|
if (world.BlockAccessor.GetBlockEntity(blockPos) is BlockEntityReusableBeehive freshBe)
|
||||||
|
{
|
||||||
|
freshBe.SwarmsDisabled = !Config.Instance.SwarmSettingAfterPlacing;
|
||||||
|
freshBe.MarkDirty(true);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (world.BlockAccessor.GetBlockEntity(blockPos) is not BlockEntityReusableBeehive be)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (byItemStack.Attributes["beehiveData"] is not ITreeAttribute beehiveData)
|
||||||
|
return;
|
||||||
|
|
||||||
|
be.BeePopulation = beehiveData.GetDouble("beePopulation");
|
||||||
|
be.HoneyProgress = beehiveData.GetDouble("honeyProgress");
|
||||||
|
be.NextSwarmAllowedTotalDays = beehiveData.GetDouble("nextSwarmAllowedTotalDays");
|
||||||
|
be.PreSwarmProgress = beehiveData.GetDouble("preSwarmProgress");
|
||||||
|
be.SwarmsDisabled = beehiveData.GetBool("swarmsDisabled");
|
||||||
|
|
||||||
|
if (beehiveData["inventory"] is ITreeAttribute inventoryTree)
|
||||||
|
be.Inventory.FromTreeAttributes(inventoryTree);
|
||||||
|
|
||||||
|
be.MarkDirty(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void GetHeldItemInfo(ItemSlot inSlot, StringBuilder dsc, IWorldAccessor world, bool withDebugInfo)
|
||||||
|
{
|
||||||
|
base.GetHeldItemInfo(inSlot, dsc, world, withDebugInfo);
|
||||||
|
|
||||||
|
if (inSlot.Itemstack?.Attributes?.HasAttribute("beehiveData") != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (inSlot.Itemstack.Attributes["beehiveData"] is not ITreeAttribute beehiveData)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double beePopulation = beehiveData.GetDouble("beePopulation");
|
||||||
|
double honeyProgress = beehiveData.GetDouble("honeyProgress");
|
||||||
|
|
||||||
|
if (beePopulation <= 0 && honeyProgress <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
dsc.AppendLine();
|
||||||
|
dsc.AppendLine("Contains:");
|
||||||
|
|
||||||
|
if (beePopulation > 0)
|
||||||
|
dsc.AppendLine($" Bee Population: {beePopulation:F0}");
|
||||||
|
|
||||||
|
if (honeyProgress > 0)
|
||||||
|
dsc.AppendLine($" Honey Progress: {honeyProgress * 100:F1}%");
|
||||||
|
|
||||||
|
if (beehiveData["inventory"] is not ITreeAttribute inventoryTree)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int frameCount = 0;
|
||||||
|
int filledFrameCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
if (!inventoryTree.HasAttribute($"slot{i}")) continue;
|
||||||
|
if (inventoryTree[$"slot{i}"] is not ITreeAttribute slotTree) continue;
|
||||||
|
if (!slotTree.HasAttribute("itemstack")) continue;
|
||||||
|
|
||||||
|
frameCount++;
|
||||||
|
var itemCode = slotTree.GetString("itemstack");
|
||||||
|
if (itemCode?.Contains("filled") == true)
|
||||||
|
filledFrameCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frameCount > 0)
|
||||||
|
dsc.AppendLine($" Frames: {frameCount} ({filledFrameCount} filled)");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetMergableQuantity(ItemStack sinkStack, ItemStack sourceStack, EnumMergePriority priority)
|
||||||
|
{
|
||||||
|
if (sinkStack?.Attributes?.HasAttribute("beehiveData") == true ||
|
||||||
|
sourceStack?.Attributes?.HasAttribute("beehiveData") == true)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return base.GetMergableQuantity(sinkStack, sourceStack, priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override WorldInteraction[] GetPlacedBlockInteractionHelp(IWorldAccessor world, BlockSelection selection, IPlayer forPlayer)
|
||||||
|
{
|
||||||
|
if (world.BlockAccessor.GetBlockEntity(selection.Position) is not BlockEntityReusableBeehive be)
|
||||||
|
return base.GetPlacedBlockInteractionHelp(world, selection, forPlayer);
|
||||||
|
|
||||||
|
var clickedSlot = GetClickedSlot(selection.HitPosition, be);
|
||||||
|
if (clickedSlot is -1)
|
||||||
|
{
|
||||||
|
var interactions = new List<WorldInteraction> { openCloseInteraction };
|
||||||
|
|
||||||
|
if (be.BeePopulation < Config.Instance.BeehiveConsideredEmptyBelowPopulation)
|
||||||
|
{
|
||||||
|
string[] existingValidSkeps = ["game:skep-reed-populated-east", "game:skep-papyrus-populated-east"];
|
||||||
|
var skepStacks = new List<ItemStack>();
|
||||||
|
|
||||||
|
foreach (var skepCode in existingValidSkeps)
|
||||||
|
{
|
||||||
|
var skepBlock = world.GetBlock(new AssetLocation(skepCode));
|
||||||
|
if (skepBlock != null)
|
||||||
|
skepStacks.Add(new ItemStack(skepBlock));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skepStacks.Count > 0)
|
||||||
|
{
|
||||||
|
interactions.Add(new WorldInteraction
|
||||||
|
{
|
||||||
|
ActionLangCode = "blockhelp-groundstorage-add",
|
||||||
|
MouseButton = EnumMouseButton.Right,
|
||||||
|
HotKeyCode = "ctrl",
|
||||||
|
Itemstacks = [.. skepStacks]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.Instance.EnableSwarms)
|
||||||
|
{
|
||||||
|
interactions.Add(new WorldInteraction
|
||||||
|
{
|
||||||
|
ActionLangCode = be.SwarmsDisabled
|
||||||
|
? "orekiwoofsbeehives:blockhelp-beehive-enable-swarm"
|
||||||
|
: "orekiwoofsbeehives:blockhelp-beehive-disable-swarm",
|
||||||
|
MouseButton = EnumMouseButton.Right,
|
||||||
|
Itemstacks = GetWrenchStacks(world)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
interactions.AddRange(base.GetPlacedBlockInteractionHelp(world, selection, forPlayer));
|
||||||
|
return [.. interactions];
|
||||||
|
}
|
||||||
|
|
||||||
|
var frameEmptyItem = new ItemStack(world.GetBlock(beehiveFrameEmptyAsset));
|
||||||
|
var frameFilledItem = new ItemStack(world.GetBlock(beehiveFrameFilledAsset));
|
||||||
|
var frameFilledFeedItem = new ItemStack(world.GetBlock(beehiveFrameFilledFeedAsset));
|
||||||
|
|
||||||
|
var amountOfFilled = be.Inventory.Count(x => IsFilledFrame(x.Itemstack));
|
||||||
|
var amountOfEmpty = be.Inventory.Count(x => x?.Itemstack?.Block?.Code.Equals(beehiveFrameEmptyAsset) == true);
|
||||||
|
|
||||||
|
var list = new List<WorldInteraction>();
|
||||||
|
|
||||||
|
AssetLocation? code = be.Inventory[clickedSlot]?.Itemstack?.Block?.Code;
|
||||||
|
var looksAtEmptySlot = be.Inventory[clickedSlot].Empty;
|
||||||
|
if (looksAtEmptySlot)
|
||||||
|
list.Add(new WorldInteraction { ActionLangCode = "blockhelp-groundstorage-add", MouseButton = EnumMouseButton.Right, Itemstacks = [frameEmptyItem, frameFilledItem, frameFilledFeedItem] });
|
||||||
|
else if (code == beehiveFrameEmptyAsset || IsFilledFrame(code))
|
||||||
|
list.Add(new WorldInteraction { ActionLangCode = "blockhelp-toolrack-take", MouseButton = EnumMouseButton.Right });
|
||||||
|
|
||||||
|
if (amountOfFilled > 0 || amountOfEmpty > 0)
|
||||||
|
list.Add(new WorldInteraction { ActionLangCode = "blockhelp-groundstorage-removebulk", HotKeyCode = "ctrl", MouseButton = EnumMouseButton.Right });
|
||||||
|
|
||||||
|
list.Add(new WorldInteraction { ActionLangCode = "heldhelp-fill", MouseButton = EnumMouseButton.Right, HotKeyCodes = ["ctrl", "shift"] });
|
||||||
|
list.AddRange(base.GetPlacedBlockInteractionHelp(world, selection, forPlayer));
|
||||||
|
return [.. list];
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnJsonTesselation(ref MeshData sourceMesh, ref int[] lightRgbsByCorner, BlockPos pos, Block[] chunkExtBlocks, int extIndex3d)
|
||||||
|
{
|
||||||
|
if (api?.World == null || closedMesh == null || openMesh == null) return;
|
||||||
|
if (api.World.BlockAccessor.GetBlockEntity(pos) is not BlockEntityReusableBeehive be)
|
||||||
|
return;
|
||||||
|
|
||||||
|
sourceMesh = be.IsOpen ? openMesh.Clone() : closedMesh.Clone();
|
||||||
|
|
||||||
|
if (!be.IsOpen)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var rotateY = GetRotationY();
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
if (be.Inventory[i].Empty) continue;
|
||||||
|
|
||||||
|
Block? frameBlock = be.Inventory[i].Itemstack?.Block;
|
||||||
|
if (frameBlock == null) continue;
|
||||||
|
|
||||||
|
ICoreClientAPI capi = (api as ICoreClientAPI)!;
|
||||||
|
capi.Tesselator.TesselateBlock(frameBlock, out MeshData frameMesh);
|
||||||
|
|
||||||
|
float slotCenterX = (2f + i * 1.5f + 0.75f) / 16f;
|
||||||
|
|
||||||
|
frameMesh.Rotate(new Vec3f(0.5f, 0.5f, 0.5f), 0, 0, GameMath.PIHALF);
|
||||||
|
frameMesh.Translate(slotCenterX - 1f + 0.03125f, 0.125f, 0);
|
||||||
|
|
||||||
|
if (rotateY != 0f)
|
||||||
|
frameMesh.Rotate(new Vec3f(0.5f, 0.5f, 0.5f), 0, rotateY, 0);
|
||||||
|
|
||||||
|
sourceMesh.AddMeshData(frameMesh);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Cuboidf[] GetSelectionBoxes(IBlockAccessor blockAccessor, BlockPos pos)
|
||||||
|
{
|
||||||
|
var rotateY = GetRotationY();
|
||||||
|
|
||||||
|
if (blockAccessor.GetBlockEntity(pos) is BlockEntityReusableBeehive be && be.IsOpen)
|
||||||
|
{
|
||||||
|
var boxes = new Cuboidf[openSelectionBoxes.Length + slotBoxes.Length];
|
||||||
|
|
||||||
|
for (int i = 0; i < openSelectionBoxes.Length; i++)
|
||||||
|
boxes[i] = RotateBox(openSelectionBoxes[i], rotateY);
|
||||||
|
|
||||||
|
for (int i = 0; i < slotBoxes.Length; i++)
|
||||||
|
boxes[openSelectionBoxes.Length + i] = RotateBox(slotBoxes[i], rotateY);
|
||||||
|
|
||||||
|
return boxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
var closedBoxes = new Cuboidf[closedSelectionBoxes.Length];
|
||||||
|
for (int i = 0; i < closedSelectionBoxes.Length; i++)
|
||||||
|
closedBoxes[i] = RotateBox(closedSelectionBoxes[i], rotateY);
|
||||||
|
return closedBoxes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private float GetRotationY()
|
||||||
|
{
|
||||||
|
var facing = Variant?["side"] ?? "north";
|
||||||
|
return GetRotationY(facing);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockFacing? GetFrontFacing()
|
||||||
|
{
|
||||||
|
var facing = Variant?["side"] ?? "north";
|
||||||
|
return facing switch
|
||||||
|
{
|
||||||
|
"north" => BlockFacing.SOUTH,
|
||||||
|
"east" => BlockFacing.WEST,
|
||||||
|
"south" => BlockFacing.NORTH,
|
||||||
|
"west" => BlockFacing.EAST,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetRotationY(string facing)
|
||||||
|
{
|
||||||
|
return facing switch
|
||||||
|
{
|
||||||
|
"north" => GameMath.PI,
|
||||||
|
"east" => GameMath.PIHALF * 3,
|
||||||
|
"south" => 0f,
|
||||||
|
"west" => GameMath.PIHALF,
|
||||||
|
_ => 0f
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Cuboidf RotateBox(Cuboidf box, float rotateY)
|
||||||
|
{
|
||||||
|
if (rotateY == 0f)
|
||||||
|
return box.Clone();
|
||||||
|
|
||||||
|
float centerX = 0.5f;
|
||||||
|
float centerZ = 0.5f;
|
||||||
|
|
||||||
|
float x1 = box.X1 - centerX;
|
||||||
|
float z1 = box.Z1 - centerZ;
|
||||||
|
float x2 = box.X2 - centerX;
|
||||||
|
float z2 = box.Z2 - centerZ;
|
||||||
|
|
||||||
|
float cos = GameMath.Cos(rotateY);
|
||||||
|
float sin = GameMath.Sin(rotateY);
|
||||||
|
|
||||||
|
float nx1 = x1 * cos - z1 * sin + centerX;
|
||||||
|
float nz1 = x1 * sin + z1 * cos + centerZ;
|
||||||
|
float nx2 = x2 * cos - z2 * sin + centerX;
|
||||||
|
float nz2 = x2 * sin + z2 * cos + centerZ;
|
||||||
|
|
||||||
|
return new Cuboidf(
|
||||||
|
Math.Min(nx1, nx2), box.Y1, Math.Min(nz1, nz2),
|
||||||
|
Math.Max(nx1, nx2), box.Y2, Math.Max(nz1, nz2)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool OnBlockInteractStart(IWorldAccessor world, IPlayer byPlayer, BlockSelection blockSel)
|
||||||
|
{
|
||||||
|
if (world.BlockAccessor.GetBlockEntity(blockSel.Position) is not BlockEntityReusableBeehive be)
|
||||||
|
return base.OnBlockInteractStart(world, byPlayer, blockSel);
|
||||||
|
|
||||||
|
bool shift = byPlayer.Entity.Controls.ShiftKey;
|
||||||
|
bool ctrl = byPlayer.Entity.Controls.CtrlKey;
|
||||||
|
ItemSlot activeSlot = byPlayer.InventoryManager.ActiveHotbarSlot;
|
||||||
|
|
||||||
|
int clickedSlot = GetClickedSlot(blockSel.HitPosition, be);
|
||||||
|
|
||||||
|
if (clickedSlot == -1 && ctrl && !activeSlot.Empty &&
|
||||||
|
activeSlot.Itemstack?.Block?.Code?.Path?.Contains("skep") == true &&
|
||||||
|
activeSlot.Itemstack?.Block?.Code?.Path?.Contains("populated") == true &&
|
||||||
|
be.BeePopulation < Config.Instance.BeehiveConsideredEmptyBelowPopulation)
|
||||||
|
{
|
||||||
|
be.BeePopulation += Config.Instance.InitialBeePopulation;
|
||||||
|
be.MarkDirty(true);
|
||||||
|
|
||||||
|
activeSlot.TakeOut(1);
|
||||||
|
activeSlot.MarkDirty();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clickedSlot == -1 && !ctrl && !shift &&
|
||||||
|
Config.Instance.EnableSwarms &&
|
||||||
|
activeSlot.Itemstack?.Item?.Code?.Path?.StartsWith("wrench") == true)
|
||||||
|
{
|
||||||
|
if (world.Side == EnumAppSide.Server)
|
||||||
|
{
|
||||||
|
be.SwarmsDisabled = !be.SwarmsDisabled;
|
||||||
|
be.MarkDirty(true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!be.IsOpen)
|
||||||
|
{
|
||||||
|
be.IsOpen = true;
|
||||||
|
be.MarkDirty(true);
|
||||||
|
world.BlockAccessor.MarkBlockDirty(blockSel.Position);
|
||||||
|
world.PlaySoundAt(new AssetLocation("game:sounds/block/door"), blockSel.Position.X, blockSel.Position.Y, blockSel.Position.Z, byPlayer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctrl && shift)
|
||||||
|
return PutInAllSlots(world, byPlayer, be);
|
||||||
|
else if (ctrl)
|
||||||
|
return TakeOutAllSlots(world, byPlayer, be);
|
||||||
|
else if (shift && clickedSlot >= 0)
|
||||||
|
return PutInSlot(world, byPlayer, be, clickedSlot);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (clickedSlot >= 0)
|
||||||
|
{
|
||||||
|
if (be.Inventory[clickedSlot].Empty && !activeSlot.Empty && activeSlot.Itemstack != null && IsFrame(activeSlot.Itemstack))
|
||||||
|
return PutInSlot(world, byPlayer, be, clickedSlot);
|
||||||
|
else if (!be.Inventory[clickedSlot].Empty)
|
||||||
|
return TakeOutSlot(world, byPlayer, be, clickedSlot);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
be.IsOpen = false;
|
||||||
|
be.MarkDirty(true);
|
||||||
|
world.BlockAccessor.MarkBlockDirty(blockSel.Position);
|
||||||
|
world.PlaySoundAt(new AssetLocation("game:sounds/block/door"), blockSel.Position.X, blockSel.Position.Y, blockSel.Position.Z, byPlayer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnBlockInteractStart(world, byPlayer, blockSel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetClickedSlot(Vec3d hitPosition, BlockEntityReusableBeehive be)
|
||||||
|
{
|
||||||
|
if (!be.IsOpen)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
Vec3d hitPos = hitPosition;
|
||||||
|
|
||||||
|
var rotateY = GetRotationY();
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
if (RotateBox(slotBoxes[i], -rotateY).Contains(hitPos.X, hitPos.Y, hitPos.Z))
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if NET10_0_OR_GREATER
|
||||||
|
public override bool DoPartialSelection(IWorldAccessor world, BlockPos pos) => true;
|
||||||
|
#else
|
||||||
|
public override bool DoParticalSelection(IWorldAccessor world, BlockPos pos) => true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public override Vec4f GetSelectionColor(ICoreClientAPI capi, BlockPos pos)
|
||||||
|
{
|
||||||
|
var baseColor = base.GetSelectionColor(capi, pos);
|
||||||
|
BlockSelection? blockSel = capi.World.Player.CurrentBlockSelection;
|
||||||
|
if (blockSel == null || !blockSel.Position.Equals(pos))
|
||||||
|
return baseColor;
|
||||||
|
|
||||||
|
if (capi.World.BlockAccessor.GetBlockEntity(pos) is not BlockEntityReusableBeehive be || !be.IsOpen)
|
||||||
|
return baseColor;
|
||||||
|
|
||||||
|
var slotIndex = GetClickedSlot(blockSel.HitPosition, be);
|
||||||
|
|
||||||
|
if (slotIndex is < 0 or >= 8)
|
||||||
|
return baseColor;
|
||||||
|
|
||||||
|
if (be.Inventory[slotIndex].Empty || !IsFilledFrame(be.Inventory[slotIndex].Itemstack))
|
||||||
|
return baseColor;
|
||||||
|
|
||||||
|
if (IsFilledFeedFrame(be.Inventory[slotIndex].Itemstack))
|
||||||
|
return new Vec4f(0.8f, 0.2f, 0f, baseColor.A);
|
||||||
|
|
||||||
|
return new Vec4f(0.6f, 0.3f, 0f, baseColor.A);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsFrame(ItemStack? stack)
|
||||||
|
{
|
||||||
|
return stack?.Block?.Code?.Path?.Contains("beehiveframe") == true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ItemStack[] GetWrenchStacks(IWorldAccessor world)
|
||||||
|
{
|
||||||
|
var wrenchWildcard = new AssetLocation("game:wrench-*");
|
||||||
|
return world.Items
|
||||||
|
.Where(item => item?.Code != null && WildcardUtil.Match(wrenchWildcard, item.Code))
|
||||||
|
.Select(item => new ItemStack(item))
|
||||||
|
.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsFilledFrame(ItemStack? stack)
|
||||||
|
{
|
||||||
|
return stack?.Block?.Code?.Path is "beehiveframe-filled" or "beehiveframe-filled-feed";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsFilledFeedFrame(ItemStack? stack)
|
||||||
|
{
|
||||||
|
return stack?.Block?.Code?.Path == "beehiveframe-filled-feed";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsFilledFrame(AssetLocation? code)
|
||||||
|
{
|
||||||
|
return code?.Path is "beehiveframe-filled" or "beehiveframe-filled-feed";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool PutInSlot(IWorldAccessor world, IPlayer byPlayer, BlockEntityReusableBeehive be, int slot)
|
||||||
|
{
|
||||||
|
ItemSlot activeSlot = byPlayer.InventoryManager.ActiveHotbarSlot;
|
||||||
|
if (activeSlot.Empty || !IsFrame(activeSlot.Itemstack))
|
||||||
|
return false;
|
||||||
|
if (!be.Inventory[slot].Empty)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var moved = activeSlot.TryPutInto(world, be.Inventory[slot], 1);
|
||||||
|
if (moved <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
be.MarkDirty(true);
|
||||||
|
world.PlaySoundAt(new AssetLocation("game:sounds/player/build"), be.Pos.X, be.Pos.Y, be.Pos.Z, byPlayer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TakeOutSlot(IWorldAccessor world, IPlayer byPlayer, BlockEntityReusableBeehive be, int slot)
|
||||||
|
{
|
||||||
|
if (be.Inventory[slot].Empty)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ItemSlot activeSlot = byPlayer.InventoryManager.ActiveHotbarSlot;
|
||||||
|
var moved = be.Inventory[slot].TryPutInto(world, activeSlot, 1);
|
||||||
|
if (moved <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
be.MarkDirty(true);
|
||||||
|
world.PlaySoundAt(new AssetLocation("game:sounds/player/build"), be.Pos.X, be.Pos.Y, be.Pos.Z, byPlayer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool PutInAllSlots(IWorldAccessor world, IPlayer byPlayer, BlockEntityReusableBeehive be)
|
||||||
|
{
|
||||||
|
var anyMoved = false;
|
||||||
|
|
||||||
|
for (var i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
if (!be.Inventory[i].Empty)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var inventorySlots = byPlayer.InventoryManager.OpenedInventories.SelectMany(inv => inv);
|
||||||
|
var a = inventorySlots?.Where(x => x != null);
|
||||||
|
var b = a?.Where(x => !x.Empty);
|
||||||
|
var c = b?.Where(x => IsFrame(x.Itemstack));
|
||||||
|
var d = c?.Where(x => x.Itemstack?.Block?.Code?.Path?.Contains("empty") == true);
|
||||||
|
var emptyFrameSlot = d?.FirstOrDefault();
|
||||||
|
|
||||||
|
if (emptyFrameSlot != null)
|
||||||
|
{
|
||||||
|
emptyFrameSlot.TryPutInto(world, be.Inventory[i], 1);
|
||||||
|
anyMoved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anyMoved)
|
||||||
|
{
|
||||||
|
be.MarkDirty(true);
|
||||||
|
world.PlaySoundAt(new AssetLocation("game:sounds/player/build"), be.Pos.X, be.Pos.Y, be.Pos.Z, byPlayer);
|
||||||
|
}
|
||||||
|
return anyMoved;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TakeOutAllSlots(IWorldAccessor world, IPlayer byPlayer, BlockEntityReusableBeehive be)
|
||||||
|
{
|
||||||
|
var hasFilledFrames = false;
|
||||||
|
var hasFeedFrames = false;
|
||||||
|
for (var i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
if (be.Inventory[i].Empty)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var itemStack = be.Inventory[i].Itemstack;
|
||||||
|
if (itemStack == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (itemStack.Block?.Code?.Path == "beehiveframe-filled")
|
||||||
|
{
|
||||||
|
hasFilledFrames = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemStack.Block?.Code?.Path == "beehiveframe-filled-feed")
|
||||||
|
hasFeedFrames = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var anyMoved = false;
|
||||||
|
for (var i = 0; i < 8; i++)
|
||||||
|
{
|
||||||
|
if (be.Inventory[i].Empty)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var isFilled = be.Inventory[i].Itemstack?.Block?.Code?.Path == "beehiveframe-filled";
|
||||||
|
var isFeed = IsFilledFeedFrame(be.Inventory[i].Itemstack);
|
||||||
|
if (hasFilledFrames && !isFilled)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!hasFilledFrames && hasFeedFrames && !isFeed)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!byPlayer.InventoryManager.TryGiveItemstack(be.Inventory[i].Itemstack))
|
||||||
|
world.SpawnItemEntity(be.Inventory[i].Itemstack, be.Pos.ToVec3d().Add(0.5, 0.5, 0.5));
|
||||||
|
|
||||||
|
be.Inventory[i].Itemstack = null;
|
||||||
|
anyMoved = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anyMoved)
|
||||||
|
{
|
||||||
|
be.MarkDirty(true);
|
||||||
|
world.PlaySoundAt(new AssetLocation("game:sounds/player/build"), be.Pos.X, be.Pos.Y, be.Pos.Z, byPlayer);
|
||||||
|
}
|
||||||
|
return anyMoved;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
OrekiWoofsBeehives/BoostCalculationType.cs
Normal file
8
OrekiWoofsBeehives/BoostCalculationType.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace OrekiWoofsBeehives;
|
||||||
|
|
||||||
|
public enum BoostCalculationType
|
||||||
|
{
|
||||||
|
Full,
|
||||||
|
LinearPopulation,
|
||||||
|
NormalizedLogk10Population,
|
||||||
|
}
|
||||||
147
OrekiWoofsBeehives/ChatCommands.cs
Normal file
147
OrekiWoofsBeehives/ChatCommands.cs
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
using OrekiWoofsBees.Common.Configs;
|
||||||
|
using OrekiWoofsBees.Common;
|
||||||
|
using Vintagestory.API.Config;
|
||||||
|
using Vintagestory.API.Client;
|
||||||
|
using Vintagestory.API.Server;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives;
|
||||||
|
|
||||||
|
public partial class OrekiWoofsBeehivesModSystem
|
||||||
|
{
|
||||||
|
private const string lang_domain = "orekiwoofsbeehives";
|
||||||
|
public bool DebugUnloadEnabled { get; private set; }
|
||||||
|
|
||||||
|
private const string meta_config_filename = "OrekiWoofsBeehivesMeta.json";
|
||||||
|
|
||||||
|
public void SetupClientCommands(ICoreClientAPI api)
|
||||||
|
{
|
||||||
|
var rootCommand = api.ChatCommands.Create("beehives");
|
||||||
|
ConfigCommands.Register(rootCommand, api.ChatCommands, lang_domain, serverSide: false, () => Config.Instance, SaveClientConfig);
|
||||||
|
|
||||||
|
rootCommand
|
||||||
|
.BeginSubCommand("tester")
|
||||||
|
.HandleWith(HandleTesterCommand)
|
||||||
|
.EndSubCommand()
|
||||||
|
|
||||||
|
.BeginSubCommand("version")
|
||||||
|
.HandleWith(HandleVersionCommand)
|
||||||
|
.EndSubCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetupServerCommands(ICoreServerAPI api)
|
||||||
|
{
|
||||||
|
var rootCommand = api.ChatCommands.Create("beehives");
|
||||||
|
ConfigCommands.Register(rootCommand, api.ChatCommands, lang_domain, serverSide: true, () => Config.Instance, SaveServerConfig);
|
||||||
|
|
||||||
|
rootCommand
|
||||||
|
.BeginSubCommand("PlantReg")
|
||||||
|
.BeginSubCommand("BlocksPerTick")
|
||||||
|
.WithDescription(Lang.Get($"{lang_domain}:plantreg-blockpertick-desc"))
|
||||||
|
.WithArgs(api.ChatCommands.Parsers.OptionalIntRange("value", 0, 1000))
|
||||||
|
.HandleWith(HandlePlantRegBlockPerTickCommand)
|
||||||
|
.EndSubCommand()
|
||||||
|
.EndSubCommand()
|
||||||
|
|
||||||
|
.BeginSubCommand("setPopulation")
|
||||||
|
.WithDescription(Lang.Get($"{lang_domain}:setpopulation-desc"))
|
||||||
|
.WithArgs(api.ChatCommands.Parsers.IntRange("value", 0, Config.Instance.MaxBeePopulation))
|
||||||
|
.HandleWith(HandleSetPopulationCommand)
|
||||||
|
.EndSubCommand()
|
||||||
|
|
||||||
|
.BeginSubCommand("debugUnload")
|
||||||
|
.WithDescription(Lang.Get($"{lang_domain}:debugunload-desc"))
|
||||||
|
.WithArgs(api.ChatCommands.Parsers.Bool("value"))
|
||||||
|
.HandleWith(HandleDebugUnloadCommand)
|
||||||
|
.EndSubCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BroadcastUnloadDebug(string message)
|
||||||
|
{
|
||||||
|
if (!DebugUnloadEnabled || serverApi == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var fullMessage = $"[Beehives CatchUp] {message}";
|
||||||
|
serverApi.BroadcastMessageToAllGroups(fullMessage, EnumChatType.Notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveClientConfig(bool _)
|
||||||
|
{
|
||||||
|
var temp = api?.LoadModConfig<Config>(config_filename) ?? new Config();
|
||||||
|
temp.InformationVerbosity = Config.Instance.InformationVerbosity;
|
||||||
|
temp.DisableServerRecommended = Config.Instance.DisableServerRecommended;
|
||||||
|
temp.BeehiveAmbientVolume = Config.Instance.BeehiveAmbientVolume;
|
||||||
|
api?.StoreModConfig(temp, config_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveServerConfig(bool _)
|
||||||
|
{
|
||||||
|
api?.StoreModConfig(Config.Instance, config_filename);
|
||||||
|
serverApi?.Network.GetChannel(CONFIG_CHANNEL_NAME).BroadcastPacket(Config.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextCommandResult HandlePlantRegBlockPerTickCommand(TextCommandCallingArgs args)
|
||||||
|
{
|
||||||
|
var registry = api?.ModLoader.GetModSystem<PlantPositionRegistryModSystem2>();
|
||||||
|
if (registry == null)
|
||||||
|
return TextCommandResult.Error(Lang.Get($"{lang_domain}:plantreg-unavailable"));
|
||||||
|
|
||||||
|
if (args.Parsers[0].IsMissing)
|
||||||
|
return TextCommandResult.Success($"PlantReg BlocksPerTick={registry.BlocksPerTick}");
|
||||||
|
|
||||||
|
if (args.Parsers[0].GetValue() is not int value)
|
||||||
|
return TextCommandResult.Error(Lang.Get($"{lang_domain}:plantreg-blockpertick-parse-error"));
|
||||||
|
|
||||||
|
registry.BlocksPerTick = value;
|
||||||
|
return TextCommandResult.Success($"PlantReg BlocksPerTick={registry.BlocksPerTick}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextCommandResult HandleSetPopulationCommand(TextCommandCallingArgs args)
|
||||||
|
{
|
||||||
|
if (args.Caller.Player is not IServerPlayer serverPlayer)
|
||||||
|
return TextCommandResult.Error("This command can only be used by a player.");
|
||||||
|
|
||||||
|
if (args.Parsers[0].GetValue() is not int value)
|
||||||
|
return TextCommandResult.Error("Couldn't parse population value.");
|
||||||
|
|
||||||
|
var blockSel = serverPlayer.CurrentBlockSelection;
|
||||||
|
if (blockSel == null)
|
||||||
|
return TextCommandResult.Error("Look at a beehive block first.");
|
||||||
|
|
||||||
|
var beehive = api?.World.BlockAccessor.GetBlockEntity(blockSel.Position) as BlockEntities.BlockEntityReusableBeehive;
|
||||||
|
if (beehive == null)
|
||||||
|
return TextCommandResult.Error("The targeted block is not a reusable beehive.");
|
||||||
|
|
||||||
|
beehive.BeePopulation = value;
|
||||||
|
beehive.MarkDirty(true);
|
||||||
|
|
||||||
|
return TextCommandResult.Success($"Bee population set to {value}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextCommandResult HandleDebugUnloadCommand(TextCommandCallingArgs args)
|
||||||
|
{
|
||||||
|
if (args.Parsers[0].GetValue() is not bool value)
|
||||||
|
return TextCommandResult.Error("Couldn't parse. Use true or false.");
|
||||||
|
|
||||||
|
DebugUnloadEnabled = value;
|
||||||
|
return TextCommandResult.Success($"debugUnload={DebugUnloadEnabled}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextCommandResult HandleTesterCommand(TextCommandCallingArgs args)
|
||||||
|
{
|
||||||
|
metaConfig.IsTester = !metaConfig.IsTester;
|
||||||
|
api?.StoreModConfig(metaConfig, meta_config_filename);
|
||||||
|
return TextCommandResult.Success(metaConfig.IsTester
|
||||||
|
? "Dev version warning disabled."
|
||||||
|
: "Dev version warning enabled.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextCommandResult HandleVersionCommand(TextCommandCallingArgs args)
|
||||||
|
{
|
||||||
|
var result = $"{Mod.Info.ModID}@{Mod.Info.Version}";
|
||||||
|
var roamingBees = api?.ModLoader.GetMod("roamingbees");
|
||||||
|
if (roamingBees != null)
|
||||||
|
result += $"\n{roamingBees.Info.ModID}@{roamingBees.Info.Version}";
|
||||||
|
return TextCommandResult.Success(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
234
OrekiWoofsBeehives/Config.cs
Normal file
234
OrekiWoofsBeehives/Config.cs
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
using OrekiWoofsBees.Common.Configs;
|
||||||
|
using ProtoBuf;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives;
|
||||||
|
|
||||||
|
[ProtoContract(SkipConstructor = true)]
|
||||||
|
public class Config
|
||||||
|
{
|
||||||
|
public static Config Instance { get; internal set; } = new();
|
||||||
|
|
||||||
|
[ProtoMember(1)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 5, Max = 70)]
|
||||||
|
public int BeehiveRadius { get; set; } = 35;
|
||||||
|
|
||||||
|
[ProtoMember(41)]
|
||||||
|
[ConfigCommand(serverSide: true)]
|
||||||
|
public bool YieldBoost { get; set; } = true;
|
||||||
|
|
||||||
|
[ProtoMember(42)]
|
||||||
|
[ConfigCommand(serverSide: true)]
|
||||||
|
public bool SpeedBoost { get; set; } = false;
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 1)]
|
||||||
|
public float GrowthSpeedBonus { get; set; } = 0.2f;
|
||||||
|
|
||||||
|
[ProtoMember(37)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0.1, Max = 3)]
|
||||||
|
public float YieldMultiplier { get; set; } = 1.2f;
|
||||||
|
|
||||||
|
[ProtoMember(31)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 5, Max = 50)]
|
||||||
|
public int PlantsFullBoostDistanceToHive { get; set; } = 20;
|
||||||
|
|
||||||
|
[ProtoMember(32)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 100, Max = 5000)]
|
||||||
|
public int MinBeesForPlantBoost { get; set; } = 1000;
|
||||||
|
|
||||||
|
[ProtoMember(46)]
|
||||||
|
[ConfigCommand(serverSide: true, AllowedValues = new[] { "Full", "LinearPopulation", "NormalizedLogk10Population" })]
|
||||||
|
public string BoostCalculation { get; set; } = "NormalizedLogk10Population";
|
||||||
|
|
||||||
|
[ProtoMember(47)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 100)]
|
||||||
|
public float PopulationPercentForMaxBoost { get; set; } = 100f;
|
||||||
|
|
||||||
|
[ProtoMember(48)]
|
||||||
|
[ConfigCommand(serverSide: true, AllowedValues = new[] { "Additive", "Max" })]
|
||||||
|
public string MultipleBeehivesBoostCombination { get; set; } = "Max";
|
||||||
|
|
||||||
|
// bee population
|
||||||
|
[ProtoMember(3)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 1000, Max = 200000)]
|
||||||
|
public int MaxBeePopulation { get; set; } = 50000;
|
||||||
|
|
||||||
|
[ProtoMember(4)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 100, Max = 50000)]
|
||||||
|
public int InitialBeePopulation { get; set; } = 5000;
|
||||||
|
|
||||||
|
[ProtoMember(49)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 200000)]
|
||||||
|
public int BeehiveConsideredEmptyBelowPopulation { get; set; } = 500;
|
||||||
|
|
||||||
|
[ProtoMember(50)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 40, Max = 100)]
|
||||||
|
public int PopulationPercentRequirementForSwarm { get; set; } = 80;
|
||||||
|
|
||||||
|
[ProtoMember(51)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 1, Max = 70)]
|
||||||
|
public int SwarmPopulationPercentage { get; set; } = 40;
|
||||||
|
|
||||||
|
[ProtoMember(54)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 70)]
|
||||||
|
public int SwarmPopulationPercentageWhenNoBeehivesAvailable { get; set; } = 5;
|
||||||
|
|
||||||
|
[ProtoMember(52)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 1, Max = 30)]
|
||||||
|
public int SwarmCooldownDays { get; set; } = 8;
|
||||||
|
|
||||||
|
[ProtoMember(53)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 100)]
|
||||||
|
public int SwarmReturnToOriginOnFailedMigrationPercent { get; set; } = 20;
|
||||||
|
|
||||||
|
[ProtoMember(56)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 1, Max = 1000)]
|
||||||
|
public int PreSwarmDurationHours { get; set; } = 40;
|
||||||
|
|
||||||
|
[ProtoMember(55)]
|
||||||
|
[ConfigCommand(serverSide: true)]
|
||||||
|
public bool EnableSwarms { get; set; } = true;
|
||||||
|
|
||||||
|
[ProtoMember(57)]
|
||||||
|
[ConfigCommand(serverSide: true)]
|
||||||
|
public bool SwarmSettingAfterPlacing { get; set; } = false;
|
||||||
|
|
||||||
|
[ProtoMember(5)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 1000)]
|
||||||
|
public int BaseDeathsPerDay { get; set; } = 100;
|
||||||
|
|
||||||
|
[ProtoMember(6)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 50)]
|
||||||
|
public int FlowerThreshold { get; set; } = 10;
|
||||||
|
|
||||||
|
[ProtoMember(7)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 500)]
|
||||||
|
public int DeathPerMissingFlower { get; set; } = 100;
|
||||||
|
|
||||||
|
[ProtoMember(8)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 1, Max = 30)]
|
||||||
|
public float DoublingTimeDays { get; set; } = 9.0f;
|
||||||
|
|
||||||
|
[ProtoMember(9)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 2000)]
|
||||||
|
public int BonusGrowthPerFilledFrame { get; set; } = 100;
|
||||||
|
|
||||||
|
// honey production
|
||||||
|
[ProtoMember(10)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 1, Max = 50)]
|
||||||
|
public float ReferenceFlowers { get; set; } = 10.0f;
|
||||||
|
|
||||||
|
[ProtoMember(11)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 1000, Max = 50000)]
|
||||||
|
public float ReferenceBees { get; set; } = 25000.0f;
|
||||||
|
|
||||||
|
[ProtoMember(12)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 1, Max = 100)]
|
||||||
|
public int MaxFlowersForHoneyProduction { get; set; } = 25;
|
||||||
|
|
||||||
|
// winter
|
||||||
|
[ProtoMember(38)]
|
||||||
|
[ConfigCommand(serverSide: true)]
|
||||||
|
public bool WinterHardMode { get; set; } = false;
|
||||||
|
|
||||||
|
[ProtoMember(39)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 50000)]
|
||||||
|
public int WinterDailyBeeDeathsWithoutFood { get; set; } = 500;
|
||||||
|
|
||||||
|
[ProtoMember(40)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 1)]
|
||||||
|
public float WinterFoodConsumptionMultiplier { get; set; } = 0.1f;
|
||||||
|
|
||||||
|
// weather effects
|
||||||
|
[ProtoMember(15)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = -20, Max = 20)]
|
||||||
|
public float MinTemperatureGrowth { get; set; } = 0f;
|
||||||
|
|
||||||
|
[ProtoMember(16)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 40)]
|
||||||
|
public float MaxTemperatureGrowth { get; set; } = 10f;
|
||||||
|
|
||||||
|
[ProtoMember(35)]
|
||||||
|
[ConfigCommand(serverSide: true)]
|
||||||
|
public bool GreenhouseAffectsBeehive { get; set; } = true;
|
||||||
|
|
||||||
|
// particles
|
||||||
|
[ProtoMember(28)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 100, Max = 5000)]
|
||||||
|
public int BeesPerParticle { get; set; } = 1000;
|
||||||
|
|
||||||
|
[ProtoMember(34)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 50)]
|
||||||
|
public int BeehiveAlwaysSpawnNumberOfBees { get; set; } = 0;
|
||||||
|
|
||||||
|
// client-side
|
||||||
|
[ProtoMember(29)]
|
||||||
|
[ConfigCommand(serverSide: false, Min = 0, Max = 4)]
|
||||||
|
public int InformationVerbosity { get; set; } = 1;
|
||||||
|
|
||||||
|
[ProtoMember(44)]
|
||||||
|
[ConfigCommand(serverSide: false)]
|
||||||
|
public bool DisableServerRecommended { get; set; } = false;
|
||||||
|
|
||||||
|
[ProtoMember(33)]
|
||||||
|
[ConfigCommand(serverSide: false, Min = 0, Max = 1)]
|
||||||
|
public float BeehiveAmbientVolume { get; set; } = 0.5f;
|
||||||
|
|
||||||
|
// server-side
|
||||||
|
[ProtoMember(43)]
|
||||||
|
[ConfigCommand(serverSide: true, AllowedValues = new[] { "ClientSide", "ServerRecommended", "ServerForced" })]
|
||||||
|
public string InformationVerbosityServerSideType { get; set; } = "ClientSide";
|
||||||
|
|
||||||
|
[ProtoMember(45)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 4)]
|
||||||
|
public int InformationVerbosityServer { get; set; } = 1;
|
||||||
|
|
||||||
|
public InformationVerbosityServerSideType GetInformationVerbosityServerSideType()
|
||||||
|
{
|
||||||
|
return InformationVerbosityServerSideType switch
|
||||||
|
{
|
||||||
|
"ClientSide" => OrekiWoofsBeehives.InformationVerbosityServerSideType.ClientSide,
|
||||||
|
"ServerRecommended" => OrekiWoofsBeehives.InformationVerbosityServerSideType.ServerRecommended,
|
||||||
|
"ServerForced" => OrekiWoofsBeehives.InformationVerbosityServerSideType.ServerForced,
|
||||||
|
_ => OrekiWoofsBeehives.InformationVerbosityServerSideType.ClientSide,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetEffectiveInformationVerbosity()
|
||||||
|
{
|
||||||
|
switch (GetInformationVerbosityServerSideType())
|
||||||
|
{
|
||||||
|
case OrekiWoofsBeehives.InformationVerbosityServerSideType.ClientSide:
|
||||||
|
return InformationVerbosity;
|
||||||
|
case OrekiWoofsBeehives.InformationVerbosityServerSideType.ServerRecommended:
|
||||||
|
if (DisableServerRecommended)
|
||||||
|
return InformationVerbosity;
|
||||||
|
return InformationVerbosityServer;
|
||||||
|
case OrekiWoofsBeehives.InformationVerbosityServerSideType.ServerForced:
|
||||||
|
return InformationVerbosityServer;
|
||||||
|
default:
|
||||||
|
return InformationVerbosity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public BoostCalculationType GetBoostCalculationType()
|
||||||
|
{
|
||||||
|
return BoostCalculation switch
|
||||||
|
{
|
||||||
|
"Full" => BoostCalculationType.Full,
|
||||||
|
"LinearPopulation" => BoostCalculationType.LinearPopulation,
|
||||||
|
"NormalizedLogk10Population" => BoostCalculationType.NormalizedLogk10Population,
|
||||||
|
_ => BoostCalculationType.Full,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public MultipleBeehivesBoostCombinationType GetMultipleBeehivesBoostCombinationType()
|
||||||
|
{
|
||||||
|
return MultipleBeehivesBoostCombination switch
|
||||||
|
{
|
||||||
|
"Additive" => MultipleBeehivesBoostCombinationType.Additive,
|
||||||
|
"Max" => MultipleBeehivesBoostCombinationType.Max,
|
||||||
|
_ => MultipleBeehivesBoostCombinationType.Max,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
12
OrekiWoofsBeehives/CropBonusType.cs
Normal file
12
OrekiWoofsBeehives/CropBonusType.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum CropBonusType
|
||||||
|
{
|
||||||
|
None = 0,
|
||||||
|
Yield = 1,
|
||||||
|
Speed = 2,
|
||||||
|
YieldAndSpeed = Yield | Speed
|
||||||
|
}
|
||||||
504
OrekiWoofsBeehives/Helpers/BeehiveInfoStringBuilder.cs
Normal file
504
OrekiWoofsBeehives/Helpers/BeehiveInfoStringBuilder.cs
Normal file
@@ -0,0 +1,504 @@
|
|||||||
|
using OrekiWoofsBeehives.BlockEntities;
|
||||||
|
using OrekiWoofsBeehives.Behaviors;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using Vintagestory.API.Config;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives.Helpers;
|
||||||
|
|
||||||
|
public static class BeehiveInfoStringBuilder
|
||||||
|
{
|
||||||
|
public static void BuildBeehiveInfo(
|
||||||
|
StringBuilder builder,
|
||||||
|
BeehiveStats stats,
|
||||||
|
BlockEntityReusableBeehive beehive)
|
||||||
|
{
|
||||||
|
var verbosity = Config.Instance.GetEffectiveInformationVerbosity();
|
||||||
|
|
||||||
|
if (verbosity == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (beehive.Api?.World != null && beehive.Api.World.Calendar.TotalDays < beehive.NextSwarmAllowedTotalDays)
|
||||||
|
builder.AppendLine(Lang.Get("orekiwoofsbeehives:beehive-info-recently-swarmed", Config.Instance.SwarmCooldownDays));
|
||||||
|
|
||||||
|
if (beehive.IsSwarmBuildingNearby())
|
||||||
|
builder.AppendLine($"<font color=\"#00bb00\">{Lang.Get("orekiwoofsbeehives:beehive-info-source-swarm-forming")}</font>");
|
||||||
|
else if (!beehive.IsReadyToStartSwarm() && beehive.IsReadyToStartSwarm(ignoreDayTime: true))
|
||||||
|
builder.AppendLine($"<font color=\"#00bb00\">{Lang.Get("orekiwoofsbeehives:beehive-info-ready-waiting-morning")}</font>");
|
||||||
|
|
||||||
|
if (!beehive.SwarmsDisabled && !beehive.IsSwarmBuildingNearby()
|
||||||
|
&& beehive.PreSwarmProgress > 0 && !beehive.IsReadyToStartSwarm(ignoreDayTime: true))
|
||||||
|
AppendPreSwarmProgress(builder, beehive, stats, verbosity);
|
||||||
|
|
||||||
|
if (verbosity >= 4)
|
||||||
|
AppendScoutingProgress(builder, beehive);
|
||||||
|
|
||||||
|
if (beehive.IsReceivingIncomingSwarm)
|
||||||
|
builder.AppendLine($"<font color=\"#00bb00\">{Lang.Get("orekiwoofsbeehives:beehive-info-incoming-swarm")}</font>");
|
||||||
|
|
||||||
|
if (!beehive.IsReceivingIncomingSwarm && beehive.BeePopulation < Config.Instance.BeehiveConsideredEmptyBelowPopulation)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"<font color=\"#ffff00\">{Lang.GetWithFallback("orekiwoofsbeehives:beehive-info-no-bees-use-skep-or-wait-for-swarm", "orekiwoofsbeehives:beehive-info-no-bees")}</font>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (beehive.IsReadyToStartSwarm())
|
||||||
|
builder.AppendLine($"<font color=\"#ffaa00\">{Lang.Get("orekiwoofsbeehives:beehive-info-ready-to-swarm")}</font>");
|
||||||
|
|
||||||
|
AppendPopulationInfo(builder, stats, beehive, verbosity);
|
||||||
|
|
||||||
|
if (verbosity >= 1)
|
||||||
|
AppendFlowerInfo(builder, stats, beehive, verbosity);
|
||||||
|
|
||||||
|
if (verbosity >= 3)
|
||||||
|
AppendFrameInfo(builder, stats);
|
||||||
|
|
||||||
|
AppendHoneyProgress(builder, stats, beehive, verbosity);
|
||||||
|
|
||||||
|
if (verbosity >= 3)
|
||||||
|
AppendFeedInfo(builder, beehive, stats);
|
||||||
|
|
||||||
|
AppendFrameStatusMessages(builder, stats, beehive, verbosity);
|
||||||
|
|
||||||
|
AppendPopulationChange(builder, stats, verbosity);
|
||||||
|
|
||||||
|
AppendCropBoostEffectiveness(builder, beehive, verbosity);
|
||||||
|
|
||||||
|
AppendTemperatureInfo(builder, stats, verbosity);
|
||||||
|
|
||||||
|
if (verbosity >= 3)
|
||||||
|
AppendHoneyProduction(builder, stats);
|
||||||
|
|
||||||
|
if (Config.Instance.EnableSwarms && beehive.SwarmsDisabled)
|
||||||
|
builder.AppendLine(Lang.Get("orekiwoofsbeehives:beehive-info-swarms-disabled"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendScoutingProgress(StringBuilder builder, BlockEntityReusableBeehive beehive)
|
||||||
|
{
|
||||||
|
var scoutingProgressString = Lang.Get("orekiwoofsbeehives:beehiveScoutingProgress");
|
||||||
|
var scanningProgress = beehive.GetScanningProgress();
|
||||||
|
var rescanningProgress = beehive.GetRescanningProgress();
|
||||||
|
|
||||||
|
builder.AppendLine($"{scoutingProgressString}: {scanningProgress * 100:F1}% (+ {rescanningProgress * 100:F1}%)");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendPreSwarmProgress(
|
||||||
|
StringBuilder builder,
|
||||||
|
BlockEntityReusableBeehive beehive,
|
||||||
|
BeehiveStats stats,
|
||||||
|
int verbosity)
|
||||||
|
{
|
||||||
|
if (verbosity == 1)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"<font color=\"#aaffaa\">{Lang.Get("orekiwoofsbeehives:beehive-info-pre-swarm-building")}</font>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
var isIncreasing = stats.Components.Temperature >= cfg.MaxTemperatureGrowth;
|
||||||
|
|
||||||
|
string detail;
|
||||||
|
if (isIncreasing)
|
||||||
|
{
|
||||||
|
var hoursLeft = (1.0 - beehive.PreSwarmProgress) * cfg.PreSwarmDurationHours;
|
||||||
|
var hoursPerDay = beehive.Api?.World?.Calendar?.HoursPerDay ?? 24.0;
|
||||||
|
|
||||||
|
if (hoursLeft < 1)
|
||||||
|
{
|
||||||
|
detail = Lang.Get("orekiwoofsbeehives:beehive-info-pre-swarm-time-lessthanhourleft");
|
||||||
|
}
|
||||||
|
else if (hoursLeft < hoursPerDay)
|
||||||
|
{
|
||||||
|
detail = Lang.Get("orekiwoofsbeehives:beehive-info-pre-swarm-time-hours", $"{hoursLeft:F0}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var daysLeft = (int)Math.Round(hoursLeft / hoursPerDay);
|
||||||
|
detail = daysLeft == 1
|
||||||
|
? Lang.Get("orekiwoofsbeehives:beehive-info-pre-swarm-time-1day")
|
||||||
|
: Lang.Get("orekiwoofsbeehives:beehive-info-pre-swarm-time-days", daysLeft);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
detail = Lang.Get("orekiwoofsbeehives:beehive-info-pre-swarm-paused");
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.AppendLine($"<font color=\"#aaffaa\">{Lang.Get("orekiwoofsbeehives:beehive-info-pre-swarm-progress", detail)}</font>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendPopulationInfo(
|
||||||
|
StringBuilder builder,
|
||||||
|
BeehiveStats stats,
|
||||||
|
BlockEntityReusableBeehive beehive,
|
||||||
|
int verbosity)
|
||||||
|
{
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
|
||||||
|
if (verbosity == 1)
|
||||||
|
{
|
||||||
|
var percentage = beehive.BeePopulation / cfg.MaxBeePopulation;
|
||||||
|
|
||||||
|
string levelKey = percentage switch
|
||||||
|
{
|
||||||
|
(<= 0) => "orekiwoofsbeehives:beehive-info-population-none",
|
||||||
|
(<= 0.25) => "orekiwoofsbeehives:beehive-info-population-low",
|
||||||
|
(<= 0.60) => "orekiwoofsbeehives:beehive-info-population-medium",
|
||||||
|
(<= 0.80) => "orekiwoofsbeehives:beehive-info-population-high",
|
||||||
|
_ => "orekiwoofsbeehives:beehive-info-population-veryhigh",
|
||||||
|
};
|
||||||
|
|
||||||
|
builder.AppendLine(Lang.Get(levelKey));
|
||||||
|
}
|
||||||
|
else if (verbosity == 2)
|
||||||
|
{
|
||||||
|
builder.AppendLine(FormatLang("orekiwoofsbeehives:beehive-info-population-v2", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["beePopulation"] = $"{beehive.BeePopulation:N0}",
|
||||||
|
["maxBeePopulation"] = $"{cfg.MaxBeePopulation:N0}",
|
||||||
|
["beeProductionMultiplier"] = $"{stats.BeeProductionMultiplier:F1}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendLine(FormatLang("orekiwoofsbeehives:beehive-info-population", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["beePopulation"] = $"{beehive.BeePopulation:N0}",
|
||||||
|
["maxBeePopulation"] = $"{cfg.MaxBeePopulation:N0}",
|
||||||
|
["beeProductionMultiplier"] = $"{stats.BeeProductionMultiplier:F1}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendFlowerInfo(
|
||||||
|
StringBuilder builder,
|
||||||
|
BeehiveStats stats,
|
||||||
|
BlockEntityReusableBeehive beehive,
|
||||||
|
int verbosity)
|
||||||
|
{
|
||||||
|
var effectiveFlowers = stats.Components.EffectiveFlowers;
|
||||||
|
|
||||||
|
if (verbosity < 3)
|
||||||
|
{
|
||||||
|
var scanningProgress = beehive.GetScanningProgress();
|
||||||
|
var scoutingSuffix = (!beehive.WasFullyScanned && scanningProgress < 1f)
|
||||||
|
? " (" + Lang.Get("orekiwoofsbeehives:beehive-info-flowers-scanning-suffix") + $" - {scanningProgress * 100:F0}%)"
|
||||||
|
: string.Empty;
|
||||||
|
|
||||||
|
builder.AppendLine(FormatLang("orekiwoofsbeehives:beehive-info-flowers-simple", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["flowersAround"] = $"{beehive.FlowersAround}",
|
||||||
|
["cropsAround"] = $"{beehive.CropsAround}"
|
||||||
|
}) + scoutingSuffix);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendLine(FormatLang("orekiwoofsbeehives:beehive-info-flowers", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["flowersAround"] = $"{beehive.FlowersAround}",
|
||||||
|
["cropsAround"] = $"{beehive.CropsAround}",
|
||||||
|
["effectiveFlowers"] = $"{effectiveFlowers:F1}",
|
||||||
|
["flowerProductionMultiplier"] = $"{stats.FlowerProductionMultiplier:F1}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (beehive.WasFullyScanned && beehive.FlowersAround.HasValue)
|
||||||
|
{
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
int flowersAround = beehive.FlowersAround.Value;
|
||||||
|
|
||||||
|
if (flowersAround < cfg.FlowerThreshold)
|
||||||
|
{
|
||||||
|
int flowersNeeded = cfg.FlowerThreshold - flowersAround;
|
||||||
|
builder.AppendLine($"<font color=\"#ff0000\">{FormatLang("orekiwoofsbeehives:beehive-info-flowers-warning-causing-deaths", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["flowersNeeded"] = $"{flowersNeeded}"
|
||||||
|
})}</font>");
|
||||||
|
}
|
||||||
|
else if (effectiveFlowers < cfg.MaxFlowersForHoneyProduction)
|
||||||
|
{
|
||||||
|
builder.AppendLine(Lang.Get("orekiwoofsbeehives:beehive-info-flowers-warning-suboptimal"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendFrameInfo(
|
||||||
|
StringBuilder builder,
|
||||||
|
BeehiveStats stats)
|
||||||
|
{
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
var filledFrames = stats.Components.FilledFramesCount;
|
||||||
|
var emptyFrames = stats.Components.EmptyFrames;
|
||||||
|
var totalFrames = stats.Components.TotalFrames;
|
||||||
|
|
||||||
|
if (filledFrames > 0 && emptyFrames > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine(FormatLang("orekiwoofsbeehives:beehive-info-frames-mixed", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["filledFrames"] = $"{filledFrames}",
|
||||||
|
["filledFrameBonus"] = $"{filledFrames * cfg.BonusGrowthPerFilledFrame:N0}",
|
||||||
|
["emptyFrames"] = $"{emptyFrames}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
else if (filledFrames > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine(FormatLang("orekiwoofsbeehives:beehive-info-frames-filled", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["filledFrames"] = $"{filledFrames}",
|
||||||
|
["filledFrameBonus"] = $"{filledFrames * cfg.BonusGrowthPerFilledFrame:N0}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
else if (emptyFrames > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine(FormatLang("orekiwoofsbeehives:beehive-info-frames-empty", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["emptyFrames"] = $"{emptyFrames}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendLine(FormatLang("orekiwoofsbeehives:beehive-info-frames-total", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["totalFrames"] = $"{totalFrames}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendHoneyProgress(
|
||||||
|
StringBuilder builder,
|
||||||
|
BeehiveStats stats,
|
||||||
|
BlockEntityReusableBeehive beehive,
|
||||||
|
int verbosity)
|
||||||
|
{
|
||||||
|
var emptyFrameSlot = beehive.GetFirstEmptyFrameSlot();
|
||||||
|
var totalFrames = stats.Components.TotalFrames;
|
||||||
|
|
||||||
|
if (verbosity == 1)
|
||||||
|
{
|
||||||
|
if (emptyFrameSlot >= 0)
|
||||||
|
{
|
||||||
|
if (stats.FramesPerDay <= 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine(Lang.Get("orekiwoofsbeehives:beehive-info-progress-v1-noproduction"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var daysToFill = (1.0 - beehive.HoneyProgress) / stats.FramesPerDay;
|
||||||
|
if (daysToFill < 1.0)
|
||||||
|
builder.AppendLine(Lang.Get("orekiwoofsbeehives:beehive-info-progress-v1-lessthanday"));
|
||||||
|
else
|
||||||
|
builder.AppendLine(FormatLang("orekiwoofsbeehives:beehive-info-progress-v1-days", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["days"] = $"{Math.Ceiling(daysToFill):F0}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (totalFrames == 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine(Lang.Get("orekiwoofsbeehives:beehive-info-no-frames"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (emptyFrameSlot >= 0)
|
||||||
|
{
|
||||||
|
if (stats.FramesPerDay > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine(FormatLang("orekiwoofsbeehives:beehive-info-progress-filling", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["honeyProgress"] = $"{beehive.HoneyProgress * 100:F1}",
|
||||||
|
["daysToFill"] = $"{(1.0 - beehive.HoneyProgress) / stats.FramesPerDay:F1}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendLine(FormatLang("orekiwoofsbeehives:beehive-info-progress-noproduction", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["honeyProgress"] = $"{beehive.HoneyProgress * 100:F1}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (totalFrames > 0 && verbosity < 3)
|
||||||
|
builder.AppendLine($"<font color=\"#00bb00\">{Lang.Get("orekiwoofsbeehives:beehive-info-all-filled")}</font>");
|
||||||
|
else if (totalFrames == 0 && verbosity < 3)
|
||||||
|
builder.AppendLine(Lang.Get("orekiwoofsbeehives:beehive-info-no-frames"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendTemperatureInfo(
|
||||||
|
StringBuilder builder,
|
||||||
|
BeehiveStats stats,
|
||||||
|
int verbosity)
|
||||||
|
{
|
||||||
|
var temperature = stats.Components.Temperature;
|
||||||
|
var temperatureMultiplier = stats.Components.TemperatureMultiplier;
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
|
||||||
|
if (verbosity is >= 1 and < 3)
|
||||||
|
{
|
||||||
|
if (temperature <= cfg.MinTemperatureGrowth)
|
||||||
|
builder.AppendLine(Lang.Get("orekiwoofsbeehives:beehive-info-temperature-overwintering"));
|
||||||
|
else if (temperature < cfg.MaxTemperatureGrowth)
|
||||||
|
builder.AppendLine(Lang.Get("orekiwoofsbeehives:beehive-info-temperature-cold"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendLine(FormatLang("orekiwoofsbeehives:beehive-info-temperature", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["temperature"] = $"{temperature:F1}",
|
||||||
|
["multiplier"] = $"{temperatureMultiplier:F2}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stats.Components.IsGreenhouse && Config.Instance.GreenhouseAffectsBeehive)
|
||||||
|
builder.AppendLine(Lang.Get("game:greenhousetempbonus"));
|
||||||
|
|
||||||
|
if (cfg.WinterHardMode
|
||||||
|
&& stats.Components.FilledFramesCount <= 0
|
||||||
|
&& temperature <= cfg.MinTemperatureGrowth)
|
||||||
|
builder.AppendLine($"<font color=\"#ff0000\">{Lang.Get("orekiwoofsbeehives:beehive-info-winter-starving")}</font>");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendFrameStatusMessages(
|
||||||
|
StringBuilder builder,
|
||||||
|
BeehiveStats stats,
|
||||||
|
BlockEntityReusableBeehive beehive,
|
||||||
|
int verbosity)
|
||||||
|
{
|
||||||
|
if (verbosity >= 3)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
var percentage = beehive.BeePopulation / cfg.MaxBeePopulation;
|
||||||
|
var filledFrames = stats.Components.FilledFramesCount;
|
||||||
|
var emptyFrames = stats.Components.EmptyFrames;
|
||||||
|
|
||||||
|
if (filledFrames > 0 && percentage > 0 && percentage <= 0.80)
|
||||||
|
{
|
||||||
|
builder.AppendLine(Lang.Get("orekiwoofsbeehives:beehive-info-frames-boosting"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emptyFrames > 0 && percentage > 0 && percentage <= 0.60)
|
||||||
|
{
|
||||||
|
builder.AppendLine(Lang.Get("orekiwoofsbeehives:beehive-info-frames-sacrificing"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendPopulationChange(StringBuilder builder, BeehiveStats stats, int verbosity)
|
||||||
|
{
|
||||||
|
if (verbosity == 1)
|
||||||
|
{
|
||||||
|
var netChange = stats.DailyNetPopulationChange;
|
||||||
|
var statusKey =
|
||||||
|
netChange < 0 ? "orekiwoofsbeehives:beehive-info-population-status-decreasing"
|
||||||
|
: netChange <= 100 ? "orekiwoofsbeehives:beehive-info-population-status-stagnant"
|
||||||
|
: netChange <= 300 ? "orekiwoofsbeehives:beehive-info-population-status-slowgrowth"
|
||||||
|
: "orekiwoofsbeehives:beehive-info-population-status-growing";
|
||||||
|
builder.AppendLine(Lang.Get(statusKey));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var changeSign = stats.DailyNetPopulationChange >= 0 ? "+" : "";
|
||||||
|
|
||||||
|
builder.AppendLine(FormatLang("orekiwoofsbeehives:beehive-info-population-change", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["changeSign"] = changeSign,
|
||||||
|
["netChange"] = $"{stats.DailyNetPopulationChange:N0}",
|
||||||
|
["dailyGrowth"] = $"{stats.Components.DailyGrowth:N0}",
|
||||||
|
["dailyDeaths"] = $"{stats.Components.DailyDeaths:N0}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendCropBoostEffectiveness(StringBuilder builder, BlockEntityReusableBeehive beehive, int verbosity)
|
||||||
|
{
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
if (!cfg.YieldBoost && !cfg.SpeedBoost)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float populationScale = BlockBehaviorBeehiveAffected.GetPopulationBoostScale(beehive.BeePopulation, cfg);
|
||||||
|
if (populationScale <= 0f)
|
||||||
|
{
|
||||||
|
builder.AppendLine($"<font color=\"#ffff00\">{Lang.Get("orekiwoofsbeehives:blockinfo-crop-boost-population-too-low")}</font>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verbosity == 1)
|
||||||
|
{
|
||||||
|
var levelText = populationScale < 0.34f
|
||||||
|
? Lang.Get("orekiwoofsbeehives:blockinfo-crop-boost-level-low")
|
||||||
|
: populationScale < 0.67f
|
||||||
|
? Lang.Get("orekiwoofsbeehives:blockinfo-crop-boost-level-medium")
|
||||||
|
: Lang.Get("orekiwoofsbeehives:blockinfo-crop-boost-level-high");
|
||||||
|
builder.AppendLine(Lang.Get("orekiwoofsbeehives:blockinfo-crop-boost-effectiveness", levelText));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultingBoostParts = new List<string>();
|
||||||
|
|
||||||
|
if (cfg.YieldBoost)
|
||||||
|
{
|
||||||
|
var yieldBonusFactor = Math.Max(0f, cfg.YieldMultiplier - 1f);
|
||||||
|
var yieldPercent = (yieldBonusFactor * populationScale * 100f).ToString("N0");
|
||||||
|
resultingBoostParts.Add(Lang.Get("orekiwoofsbeehives:blockinfo-crop-boost-result-yield", yieldPercent));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfg.SpeedBoost)
|
||||||
|
{
|
||||||
|
var speedPercent = (cfg.GrowthSpeedBonus * populationScale * 100f).ToString("N0");
|
||||||
|
resultingBoostParts.Add(Lang.Get("orekiwoofsbeehives:blockinfo-crop-boost-result-speed", speedPercent));
|
||||||
|
}
|
||||||
|
|
||||||
|
var effectivenessPercent = (populationScale * 100f).ToString("N0");
|
||||||
|
var cropBoostLine = Lang.Get("orekiwoofsbeehives:blockinfo-crop-boost-effectiveness", effectivenessPercent + "%");
|
||||||
|
|
||||||
|
if (resultingBoostParts.Count > 0)
|
||||||
|
cropBoostLine += " (" + string.Join(", ", resultingBoostParts) + ")";
|
||||||
|
|
||||||
|
builder.AppendLine(cropBoostLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendHoneyProduction(StringBuilder builder, BeehiveStats stats)
|
||||||
|
{
|
||||||
|
if (stats.FramesPerDay > 0)
|
||||||
|
{
|
||||||
|
builder.AppendLine(FormatLang("orekiwoofsbeehives:beehive-info-honey-production", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["framesPerDay"] = $"{stats.FramesPerDay:F2}",
|
||||||
|
["daysPerFrame"] = $"{1.0 / stats.FramesPerDay:F1}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
builder.AppendLine(FormatLang("orekiwoofsbeehives:beehive-info-honey-production-simple", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["framesPerDay"] = $"{stats.FramesPerDay:F2}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AppendFeedInfo(StringBuilder builder, BlockEntityReusableBeehive beehive, BeehiveStats stats)
|
||||||
|
{
|
||||||
|
if (!beehive.TryGetCurrentFeedStatus(out double remaining))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var daysPerFrame = stats.FeedConsumedPerDay > 0
|
||||||
|
? 1.0 / stats.FeedConsumedPerDay
|
||||||
|
: 0.0;
|
||||||
|
|
||||||
|
builder.AppendLine(FormatLang("orekiwoofsbeehives:beehive-info-feed-current", new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
["feedRemainingPercent"] = $"{remaining * 100:F0}",
|
||||||
|
["daysPerFrame"] = $"{daysPerFrame:F1}"
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatLang(string langKey, Dictionary<string, string> values)
|
||||||
|
{
|
||||||
|
var result = Lang.Get(langKey);
|
||||||
|
foreach (var kvp in values)
|
||||||
|
result = result.Replace($">>>{kvp.Key}<<<", kvp.Value);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
8
OrekiWoofsBeehives/InformationVerbosityServerSideType.cs
Normal file
8
OrekiWoofsBeehives/InformationVerbosityServerSideType.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace OrekiWoofsBeehives;
|
||||||
|
|
||||||
|
public enum InformationVerbosityServerSideType
|
||||||
|
{
|
||||||
|
ClientSide,
|
||||||
|
ServerRecommended,
|
||||||
|
ServerForced,
|
||||||
|
}
|
||||||
7
OrekiWoofsBeehives/MetaConfig.cs
Normal file
7
OrekiWoofsBeehives/MetaConfig.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace OrekiWoofsBeehives;
|
||||||
|
|
||||||
|
public class MetaConfig
|
||||||
|
{
|
||||||
|
public bool IsTester { get; set; }
|
||||||
|
public string? ModVersion { get; set; }
|
||||||
|
}
|
||||||
179
OrekiWoofsBeehives/ModConfigSetup.cs
Normal file
179
OrekiWoofsBeehives/ModConfigSetup.cs
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
using ConfigLib;
|
||||||
|
using System;
|
||||||
|
using Vintagestory.API.Client;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.Server;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives;
|
||||||
|
|
||||||
|
public partial class OrekiWoofsBeehivesModSystem : ModSystem
|
||||||
|
{
|
||||||
|
private ConfigLibModSystem? configLibSystem;
|
||||||
|
public const string CONFIG_CHANNEL_NAME = "orekiwoofsbeehives-config";
|
||||||
|
private const string config_filename = "OrekiWoofsBeehives.json";
|
||||||
|
|
||||||
|
private void SubscribeToConfigChange(ICoreAPI api)
|
||||||
|
{
|
||||||
|
configLibSystem = api.ModLoader.GetModSystem<ConfigLibModSystem>();
|
||||||
|
if (configLibSystem == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
configLibSystem.SettingChanged += OnSettingChanged;
|
||||||
|
configLibSystem.ConfigsLoaded += OnConfigsLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnsubscribeFromConfigChange()
|
||||||
|
{
|
||||||
|
if (configLibSystem is null)
|
||||||
|
return;
|
||||||
|
configLibSystem.SettingChanged -= OnSettingChanged;
|
||||||
|
configLibSystem.ConfigsLoaded -= OnConfigsLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConfigsLoaded()
|
||||||
|
{
|
||||||
|
configLibSystem?.GetConfig("orekiwoofsbeehives")?.AssignSettingsValues(Config.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSettingChanged(string domain, IConfig config, ISetting setting)
|
||||||
|
{
|
||||||
|
if (domain != "orekiwoofsbeehives")
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (api != null)
|
||||||
|
Config.Instance = api.LoadModConfig<Config>(config_filename) ?? Config.Instance;
|
||||||
|
setting.AssignSettingValue(Config.Instance);
|
||||||
|
serverApi?.Network.GetChannel(CONFIG_CHANNEL_NAME).BroadcastPacket(Config.Instance);
|
||||||
|
if (api?.Side == EnumAppSide.Server)
|
||||||
|
api.StoreModConfig(Config.Instance, config_filename);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Mod.Logger.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeServerConfig(ICoreServerAPI api)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var oldCropBonus = GetBackCompatibilityCropBonus(api);
|
||||||
|
Config.Instance = api.LoadModConfig<Config>(config_filename);
|
||||||
|
Config.Instance ??= new Config();
|
||||||
|
|
||||||
|
ConvertOldCropBonus(oldCropBonus, Config.Instance);
|
||||||
|
|
||||||
|
api.StoreModConfig(Config.Instance, config_filename);
|
||||||
|
|
||||||
|
if (oldCropBonus is not null && api.ModLoader.IsModEnabled("configlib"))
|
||||||
|
ReloadConfigForConfigLib(api);
|
||||||
|
|
||||||
|
api.Event.PlayerJoin += OnPlayerJoin;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Mod.Logger.Error("Could not load config! Loading default settings instead.");
|
||||||
|
Mod.Logger.Error(e);
|
||||||
|
Config.Instance = new Config();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeClientConfig(ICoreClientAPI api)
|
||||||
|
{
|
||||||
|
Config? clientSideConfig;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
clientSideConfig = (Config?)api.LoadModConfig<Config>(config_filename);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Mod.Logger.Error("Could not load client-side config! Loading default client-side settings instead.");
|
||||||
|
Mod.Logger.Error(e);
|
||||||
|
clientSideConfig = new Config();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientSideConfig is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Config.Instance.BeehiveAmbientVolume = clientSideConfig.BeehiveAmbientVolume;
|
||||||
|
Config.Instance.InformationVerbosity = clientSideConfig.InformationVerbosity;
|
||||||
|
Config.Instance.DisableServerRecommended = clientSideConfig.DisableServerRecommended;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReloadConfigForConfigLib(ICoreAPI api)
|
||||||
|
{
|
||||||
|
var configLibModSystem = api.ModLoader.GetModSystem<ConfigLibModSystem>();
|
||||||
|
var configLibConfig = configLibModSystem.GetConfig("orekiwoofsbeehives");
|
||||||
|
configLibConfig?.ReadFromFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlayerJoin(IServerPlayer byPlayer)
|
||||||
|
{
|
||||||
|
serverApi!.Network.GetChannel(CONFIG_CHANNEL_NAME).SendPacket(Config.Instance, byPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConfigReceivedFromServer(Config packet)
|
||||||
|
{
|
||||||
|
var currentConfig = Config.Instance;
|
||||||
|
Config.Instance = packet;
|
||||||
|
Config.Instance.InformationVerbosity = currentConfig.InformationVerbosity;
|
||||||
|
Config.Instance.DisableServerRecommended = currentConfig.DisableServerRecommended;
|
||||||
|
Config.Instance.BeehiveAmbientVolume = currentConfig.BeehiveAmbientVolume;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CropBonusType? GetBackCompatibilityCropBonus(ICoreAPI api)
|
||||||
|
{
|
||||||
|
var cfgJson = api.LoadModConfig(config_filename);
|
||||||
|
if (cfgJson is null || !cfgJson.KeyExists("CropBonus"))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var cropBonusObj = cfgJson["CropBonus"];
|
||||||
|
if (cropBonusObj is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var cropBonusInt = cropBonusObj.AsInt(-1);
|
||||||
|
if (cropBonusInt >= 0)
|
||||||
|
{
|
||||||
|
var cropBonus = (CropBonusType)cropBonusInt;
|
||||||
|
return cropBonus;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cropBonusStr = cropBonusObj.AsString(null);
|
||||||
|
return cropBonusStr switch
|
||||||
|
{
|
||||||
|
"Yield" => CropBonusType.Yield,
|
||||||
|
"Speed" => CropBonusType.Speed,
|
||||||
|
"YieldAndSpeed" => CropBonusType.YieldAndSpeed,
|
||||||
|
"None" => CropBonusType.None,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ConvertOldCropBonus(CropBonusType? cropBonus, Config config)
|
||||||
|
{
|
||||||
|
if (cropBonus == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
config.SpeedBoost = false;
|
||||||
|
config.YieldBoost = false;
|
||||||
|
switch (cropBonus.Value)
|
||||||
|
{
|
||||||
|
case CropBonusType.Yield:
|
||||||
|
config.YieldBoost = true;
|
||||||
|
break;
|
||||||
|
case CropBonusType.Speed:
|
||||||
|
config.SpeedBoost = true;
|
||||||
|
break;
|
||||||
|
case CropBonusType.YieldAndSpeed:
|
||||||
|
config.YieldBoost = true;
|
||||||
|
config.SpeedBoost = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Mod.Logger.Event($"Converted CropBonus:{cropBonus} to YieldBoost:{config.YieldBoost}, SpeedBoost:{config.SpeedBoost}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace OrekiWoofsBeehives;
|
||||||
|
|
||||||
|
public enum MultipleBeehivesBoostCombinationType
|
||||||
|
{
|
||||||
|
Additive,
|
||||||
|
Max,
|
||||||
|
}
|
||||||
86
OrekiWoofsBeehives/OrekiWoofsBeehives.csproj
Normal file
86
OrekiWoofsBeehives/OrekiWoofsBeehives.csproj
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<OutputPath>bin\$(Configuration)\Mods\orekiwoofsbeehives</OutputPath>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<Configurations>Debug;Release;Debug22</Configurations>
|
||||||
|
<VintageStoryDataPathArgs></VintageStoryDataPathArgs>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)' == 'Debug22'">
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<VINTAGE_STORY>$(VINTAGE_STORY_22PRE2)</VINTAGE_STORY>
|
||||||
|
<VintageStoryDataPathArgs> --dataPath "..\VintageStoryDataBeehives\Data22"</VintageStoryDataPathArgs>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="VintagestoryAPI">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/VintagestoryAPI.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VSSurvivalMod">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/Mods/VSSurvivalMod.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VSEssentials">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/Mods/VSEssentials.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VSCreativeMod">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/Mods/VSCreativeMod.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Newtonsoft.Json">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/Lib/Newtonsoft.Json.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="0Harmony">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/Lib/0Harmony.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VintagestoryLib">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/VintagestoryLib.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="protobuf-net">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/Lib/protobuf-net.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="cairo-sharp">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/Lib/cairo-sharp.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Data.Sqlite">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/Lib/Microsoft.Data.Sqlite.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="configlib">
|
||||||
|
<HintPath>E:\Code\VintageStory\configlib_1.10.14\configlib.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="modinfo.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="modicon.png" Condition="Exists('modicon.png')">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="..\OrekiWoofsBees.Common\**\*.cs" Exclude="..\OrekiWoofsBees.Common\bin\**\*;..\OrekiWoofsBees.Common\obj\**\*">
|
||||||
|
<Link>Common\%(RecursiveDir)%(Filename)%(Extension)</Link>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
132
OrekiWoofsBeehives/OrekiWoofsBeehivesModSystem.cs
Normal file
132
OrekiWoofsBeehives/OrekiWoofsBeehivesModSystem.cs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using OrekiWoofsBeehives.Behaviors;
|
||||||
|
using OrekiWoofsBeehives.BlockEntities;
|
||||||
|
using OrekiWoofsBeehives.Blocks;
|
||||||
|
using OrekiWoofsBeehives.Utilities;
|
||||||
|
using System;
|
||||||
|
using Vintagestory.API.Client;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.Server;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives;
|
||||||
|
|
||||||
|
public partial class OrekiWoofsBeehivesModSystem : ModSystem
|
||||||
|
{
|
||||||
|
private Harmony? _harmony;
|
||||||
|
private ICoreServerAPI? serverApi;
|
||||||
|
private ICoreAPI? api;
|
||||||
|
private MetaConfig metaConfig = new();
|
||||||
|
|
||||||
|
public BeehiveRegistry BeehiveRegistry { get; } = new();
|
||||||
|
public VanillaSkepRegistry VanillaSkepRegistry { get; } = new();
|
||||||
|
|
||||||
|
// Called on server and client
|
||||||
|
// Useful for registering block/entity classes on both sides
|
||||||
|
public override void Start(ICoreAPI api)
|
||||||
|
{
|
||||||
|
this.api = api;
|
||||||
|
api.RegisterBlockBehaviorClass(nameof(BlockBehaviorBeehiveAffected), typeof(BlockBehaviorBeehiveAffected));
|
||||||
|
api.RegisterBlockEntityBehaviorClass(nameof(BlockEntityBehaviorBeehiveYieldMultiplier), typeof(BlockEntityBehaviorBeehiveYieldMultiplier));
|
||||||
|
api.RegisterBlockEntityBehaviorClass(nameof(BlockEntityBehaviorVanillaSkepSwarmTarget), typeof(BlockEntityBehaviorVanillaSkepSwarmTarget));
|
||||||
|
|
||||||
|
api.RegisterBlockClass(nameof(BlockBeeSwarm), typeof(BlockBeeSwarm));
|
||||||
|
api.RegisterBlockClass(nameof(BlockBeehiveFrame), typeof(BlockBeehiveFrame));
|
||||||
|
|
||||||
|
api.RegisterBlockClass(nameof(BlockReusableBeehive), typeof(BlockReusableBeehive));
|
||||||
|
api.RegisterBlockEntityClass(nameof(BlockEntityReusableBeehive), typeof(BlockEntityReusableBeehive));
|
||||||
|
api.RegisterBlockEntityClass(nameof(BlockEntityBeeSwarm), typeof(BlockEntityBeeSwarm));
|
||||||
|
|
||||||
|
api.Network.RegisterChannel(CONFIG_CHANNEL_NAME).RegisterMessageType<Config>();
|
||||||
|
|
||||||
|
_harmony = new Harmony("com.orekiwoof.beehives");
|
||||||
|
_harmony.PatchAllUncategorized();
|
||||||
|
if (api.ModLoader.GetMod("game").Info.Version.StartsWith("1.22"))
|
||||||
|
_harmony.PatchCategory("1.22");
|
||||||
|
if (api.ModLoader.GetMod("game").Info.Version.StartsWith("1.21"))
|
||||||
|
_harmony.PatchCategory("1.21");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override double ExecuteOrder() => 0.12;
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
_harmony?.UnpatchAll("com.orekiwoof.beehives");
|
||||||
|
if (serverApi != null)
|
||||||
|
serverApi.Event.PlayerJoin -= OnPlayerJoin;
|
||||||
|
if (api?.ModLoader.IsModSystemEnabled("ConfigLib.ConfigLibModSystem") == true)
|
||||||
|
UnsubscribeFromConfigChange();
|
||||||
|
base.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void StartServerSide(ICoreServerAPI api)
|
||||||
|
{
|
||||||
|
serverApi = api;
|
||||||
|
InitializeServerConfig(api);
|
||||||
|
SetupServerCommands(api);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (api.ModLoader.IsModSystemEnabled("ConfigLib.ConfigLibModSystem"))
|
||||||
|
SubscribeToConfigChange(api);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Mod.Logger.VerboseDebug("Failed to subscribe to config change");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void StartClientSide(ICoreClientAPI api)
|
||||||
|
{
|
||||||
|
InitializeClientConfig(api);
|
||||||
|
SetupClientCommands(api);
|
||||||
|
api.Network.GetChannel(CONFIG_CHANNEL_NAME)?.SetMessageHandler<Config>(OnConfigReceivedFromServer);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (api.ModLoader.IsModSystemEnabled("ConfigLib.ConfigLibModSystem"))
|
||||||
|
SubscribeToConfigChange(api);
|
||||||
|
}
|
||||||
|
catch (Exception)
|
||||||
|
{
|
||||||
|
Mod.Logger.VerboseDebug("Failed to subscribe to config change");
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadMetaConfig(api);
|
||||||
|
ShowDevVersionWarningIfNeeded(api);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadMetaConfig(ICoreClientAPI api)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
metaConfig = api.LoadModConfig<MetaConfig>(meta_config_filename) ?? new MetaConfig();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Mod.Logger.Warning("Could not load meta config: {0}", e.Message);
|
||||||
|
metaConfig = new MetaConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (metaConfig.ModVersion != Mod.Info.Version)
|
||||||
|
{
|
||||||
|
metaConfig.ModVersion = Mod.Info.Version;
|
||||||
|
api.StoreModConfig(metaConfig, meta_config_filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ShowDevVersionWarningIfNeeded(ICoreClientAPI api)
|
||||||
|
{
|
||||||
|
if (!Mod.Info.Version.Contains("-dev"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (metaConfig.IsTester)
|
||||||
|
return;
|
||||||
|
|
||||||
|
api.Event.LevelFinalize += () =>
|
||||||
|
{
|
||||||
|
api.ShowChatMessage("This is a development version of OrekiWoof's Beehives. It can break and crash. When reporting issues, mention the exact version of this mod (and Roaming Bees mod if installed).");
|
||||||
|
api.ShowChatMessage("To disable this warning, use \".beehives tester\"");
|
||||||
|
api.ShowChatMessage("To check mod versions, use \".beehives version\"");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
57
OrekiWoofsBeehives/Patches/BEFarmland_GetGrowthRate_Patch.cs
Normal file
57
OrekiWoofsBeehives/Patches/BEFarmland_GetGrowthRate_Patch.cs
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
using OrekiWoofsBeehives.Utilities;
|
||||||
|
using OrekiWoofsBeehives.Behaviors;
|
||||||
|
using Vintagestory.GameContent;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives.Patches;
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(BlockEntityFarmland), nameof(BlockEntityFarmland.GetGrowthRate), [typeof(EnumSoilNutrient)])]
|
||||||
|
[HarmonyPatchCategory("1.21")]
|
||||||
|
public static class BEFarmland_GetGrowthRate_Patch
|
||||||
|
{
|
||||||
|
[HarmonyPostfix]
|
||||||
|
public static void Postfix(BlockEntityFarmland __instance, ref float __result)
|
||||||
|
{
|
||||||
|
GetGrowthRateCommon.CommonMethod(__instance, ref __result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG22
|
||||||
|
[HarmonyPatch(typeof(BlockEntitySoilNutrition), nameof(BlockEntitySoilNutrition.GetGrowthRate), [typeof(EnumSoilNutrient)])]
|
||||||
|
[HarmonyPatchCategory("1.22")]
|
||||||
|
public static class BESoilNutrition_GetGrowthRate_Patch
|
||||||
|
{
|
||||||
|
[HarmonyPostfix]
|
||||||
|
public static void Postfix(BlockEntitySoilNutrition __instance, ref float __result)
|
||||||
|
{
|
||||||
|
GetGrowthRateCommon.CommonMethod(__instance, ref __result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public static class GetGrowthRateCommon
|
||||||
|
{
|
||||||
|
|
||||||
|
public static void CommonMethod(BlockEntity __instance, ref float __result)
|
||||||
|
{
|
||||||
|
BlockPos? cropPos = __instance?.Pos?.UpCopy();
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
if (!cfg.SpeedBoost)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var registry = __instance?.Api?.GetOrekiWoofsBeehives()?.BeehiveRegistry;
|
||||||
|
|
||||||
|
if (registry is null || cropPos is null || __instance is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float totalBeeEffectiveness = BlockBehaviorBeehiveAffected.GetBeehiveBoostEffectiveness(__instance.Api, registry.BeehivePositions, cropPos);
|
||||||
|
if (totalBeeEffectiveness > 0f)
|
||||||
|
{
|
||||||
|
float multiplier = 1.0f + (totalBeeEffectiveness * cfg.GrowthSpeedBonus);
|
||||||
|
__result *= multiplier;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
68
OrekiWoofsBeehives/Patches/BlockCrop_GetDrops_Patch.cs
Normal file
68
OrekiWoofsBeehives/Patches/BlockCrop_GetDrops_Patch.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using OrekiWoofsBeehives.Behaviors;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
using Vintagestory.GameContent;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives.Patches;
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(BlockCrop), nameof(BlockCrop.GetDrops), argumentTypes: [typeof(IWorldAccessor), typeof(BlockPos), typeof(IPlayer), typeof(float)])]
|
||||||
|
public static class BlockCrop_GetDrops_Patch
|
||||||
|
{
|
||||||
|
[HarmonyPostfix]
|
||||||
|
public static void Postfix(BlockCrop __instance, IWorldAccessor world, BlockPos pos, ref ItemStack[] __result)
|
||||||
|
{
|
||||||
|
if (!ShouldApplyYieldBonus(__instance, world, pos, __result, out var yieldBonus))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var additionalMultiplier = yieldBonus;
|
||||||
|
|
||||||
|
var finalDrops = new List<ItemStack>(__result.Length);
|
||||||
|
foreach (var drop in __result)
|
||||||
|
{
|
||||||
|
if (drop == null || drop.StackSize <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var newDrop = drop.Clone();
|
||||||
|
float additional = newDrop.StackSize * additionalMultiplier;
|
||||||
|
int guaranteed = (int)MathF.Floor(additional);
|
||||||
|
float chance = additional - guaranteed;
|
||||||
|
int bonus = guaranteed;
|
||||||
|
if (chance > 0f && world.Rand.NextDouble() < chance)
|
||||||
|
bonus += 1;
|
||||||
|
|
||||||
|
newDrop.StackSize += bonus;
|
||||||
|
if (newDrop.StackSize > 0)
|
||||||
|
finalDrops.Add(newDrop);
|
||||||
|
}
|
||||||
|
|
||||||
|
__result = [.. finalDrops];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldApplyYieldBonus(BlockCrop crop, IWorldAccessor world, BlockPos pos, ItemStack[]? drops, out float yieldBonus)
|
||||||
|
{
|
||||||
|
yieldBonus = 0f;
|
||||||
|
|
||||||
|
if (drops == null || drops.Length == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
if (!cfg.YieldBoost)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
yieldBonus = Math.Max(0f, cfg.YieldMultiplier - 1f);
|
||||||
|
if (yieldBonus <= 0f)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (world.BlockAccessor.GetBlockEntity(pos.DownCopy()) is not BlockEntityFarmland farmland)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var tracker = farmland.Block.GetBEBehavior<BlockEntityBehaviorBeehiveYieldMultiplier>(farmland.Pos);
|
||||||
|
if (tracker is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return tracker.IsPollinated(crop.CurrentCropStage);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
OrekiWoofsBeehives/Properties/launchSettings.json
Normal file
16
OrekiWoofsBeehives/Properties/launchSettings.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"Client": {
|
||||||
|
"commandName": "Executable",
|
||||||
|
"executablePath": "dotnet",
|
||||||
|
"commandLineArgs": "\"$(VINTAGE_STORY)/Vintagestory.dll\" --tracelog --addModPath \"$(ProjectDir)/bin/$(Configuration)/Mods\" --addOrigin \"$(ProjectDir)/assets\"$(VintageStoryDataPathArgs)",
|
||||||
|
"workingDirectory": "$(VINTAGE_STORY)"
|
||||||
|
},
|
||||||
|
"Server": {
|
||||||
|
"commandName": "Executable",
|
||||||
|
"executablePath": "dotnet",
|
||||||
|
"commandLineArgs": "\"$(VINTAGE_STORY)/VintagestoryServer.dll\" --tracelog --addModPath \"$(ProjectDir)/bin/$(Configuration)/Mods\" --addOrigin \"$(ProjectDir)/assets\"$(VintageStoryDataPathArgs)",
|
||||||
|
"workingDirectory": "$(VINTAGE_STORY)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
11
OrekiWoofsBeehives/Utilities/ApiExtensions.cs
Normal file
11
OrekiWoofsBeehives/Utilities/ApiExtensions.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using OrekiWoofsBees.Common;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives.Utilities;
|
||||||
|
|
||||||
|
internal static class ApiExtensions
|
||||||
|
{
|
||||||
|
public static OrekiWoofsBeehivesModSystem? GetOrekiWoofsBeehives(this ICoreAPI api) => api.ModLoader.GetModSystem<OrekiWoofsBeehivesModSystem>();
|
||||||
|
|
||||||
|
public static IPlantPositionRegistry? GetPlantPositionRegistry(this ICoreAPI api) => api.ModLoader.GetModSystem<PlantPositionRegistryModSystem2>();
|
||||||
|
}
|
||||||
20
OrekiWoofsBeehives/Utilities/BeehiveRegistry.cs
Normal file
20
OrekiWoofsBeehives/Utilities/BeehiveRegistry.cs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
using OrekiWoofsBeehives.BlockEntities;
|
||||||
|
using OrekiWoofsBees.Common;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives.Utilities;
|
||||||
|
|
||||||
|
public class BeehiveRegistry
|
||||||
|
{
|
||||||
|
private readonly List<StructVec3i> beehivePositions = [];
|
||||||
|
public IEnumerable<StructVec3i> BeehivePositions => beehivePositions;
|
||||||
|
|
||||||
|
public void Register(BlockEntityReusableBeehive beehive) => beehivePositions.Add(new(beehive.Pos.X, beehive.Pos.Y, beehive.Pos.Z));
|
||||||
|
|
||||||
|
public void Unregister(BlockPos pos)
|
||||||
|
{
|
||||||
|
var position = new StructVec3i(pos.X, pos.Y, pos.Z);
|
||||||
|
beehivePositions.Remove(position);
|
||||||
|
}
|
||||||
|
}
|
||||||
24
OrekiWoofsBeehives/Utilities/VanillaSkepRegistry.cs
Normal file
24
OrekiWoofsBeehives/Utilities/VanillaSkepRegistry.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using OrekiWoofsBees.Common;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBeehives.Utilities;
|
||||||
|
|
||||||
|
public class VanillaSkepRegistry
|
||||||
|
{
|
||||||
|
private readonly Dictionary<StructVec3i, string> emptySkepPositions = [];
|
||||||
|
|
||||||
|
public IEnumerable<KeyValuePair<StructVec3i, string>> Entries => emptySkepPositions;
|
||||||
|
|
||||||
|
public void RegisterVanillaSkep(BlockPos pos, string populatedSkepCode)
|
||||||
|
{
|
||||||
|
var key = new StructVec3i(pos.X, pos.Y, pos.Z);
|
||||||
|
emptySkepPositions[key] = populatedSkepCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnregisterVanillaSkep(BlockPos pos)
|
||||||
|
{
|
||||||
|
var key = new StructVec3i(pos.X, pos.Y, pos.Z);
|
||||||
|
emptySkepPositions.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"code": "beehive",
|
||||||
|
"class": "BlockReusableBeehive",
|
||||||
|
"entityClass": "BlockEntityReusableBeehive",
|
||||||
|
"variantgroups": [
|
||||||
|
{ "code": "side", "loadFromProperties": "abstract/horizontalorientation" }
|
||||||
|
],
|
||||||
|
"behaviors": [{"name": "HorizontalOrientable", "properties": { "dropBlockFace": "north", "orientate": true }}],
|
||||||
|
"blockmaterial": "Wood",
|
||||||
|
"creativeinventory": {
|
||||||
|
"general": ["*-north"],
|
||||||
|
"decorative": ["*-north"]
|
||||||
|
},
|
||||||
|
"lightAbsorption": 0,
|
||||||
|
"drop": {
|
||||||
|
"code": "orekiwoofsbeehives:beehive-north",
|
||||||
|
"type": "block"
|
||||||
|
},
|
||||||
|
"resistance": 2.5,
|
||||||
|
"collisionbox": { "x1": 0, "y1": 0, "z1": 0, "x2": 1, "y2": 1, "z2": 1 },
|
||||||
|
"attributes": {
|
||||||
|
"beeHive": true,
|
||||||
|
"handbook": {
|
||||||
|
"extraSections": [
|
||||||
|
{ "title": "orekiwoofsbeehives:handbook-beehive-title", "text": "orekiwoofsbeehives:handbook-beehive" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"shapebytype": {
|
||||||
|
"*-north": { "base": "orekiwoofsbeehives:block/beehive-closed", "rotateY": 0 },
|
||||||
|
"*-east": { "base": "orekiwoofsbeehives:block/beehive-closed", "rotateY": 270 },
|
||||||
|
"*-south": { "base": "orekiwoofsbeehives:block/beehive-closed", "rotateY": 180 },
|
||||||
|
"*-west": { "base": "orekiwoofsbeehives:block/beehive-closed", "rotateY": 90 }
|
||||||
|
},
|
||||||
|
"textures": {
|
||||||
|
"all": {
|
||||||
|
"base": "game:block/wood/henbox/sides"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sounds": {
|
||||||
|
"place": "game:block/planks",
|
||||||
|
"break": "game:block/planks",
|
||||||
|
"hit": "game:block/planks",
|
||||||
|
"ambient": "game:creature/beehive"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
{
|
||||||
|
"code": "beehiveframe",
|
||||||
|
"class": "BlockBeehiveFrame",
|
||||||
|
"behaviors": [
|
||||||
|
{
|
||||||
|
"name": "GroundStorable",
|
||||||
|
"properties": {
|
||||||
|
"layout": "Stacking",
|
||||||
|
"stackingCapacity": 16,
|
||||||
|
"cbScaleYByLayer": 1,
|
||||||
|
"upSolid": true,
|
||||||
|
"placeRemoveSound": "game:block/planks",
|
||||||
|
"stackingModel": "orekiwoofsbeehives:block/beehiveframepile",
|
||||||
|
"modelItemsToStackSizeRatio": 5,
|
||||||
|
"stackingTextures": {
|
||||||
|
"frame": "game:block/wood-generic",
|
||||||
|
"center": "game:block/linen"
|
||||||
|
},
|
||||||
|
"stackingTexturesByType": {
|
||||||
|
"beehiveframe-filled": {
|
||||||
|
"center": "game:block/resin"
|
||||||
|
},
|
||||||
|
"beehiveframe-filled-feed": {
|
||||||
|
"center": "game:block/food/grain/flax"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ "name": "Unplaceable" },
|
||||||
|
{ "name": "RightClickPickup" }
|
||||||
|
],
|
||||||
|
"variantgroups": [
|
||||||
|
{ "code": "state", "states": ["empty", "filled", "filled-feed"] }
|
||||||
|
],
|
||||||
|
"maxstacksize": 64,
|
||||||
|
"creativeinventory": {
|
||||||
|
"general": ["*"],
|
||||||
|
"items": ["*"]
|
||||||
|
},
|
||||||
|
"shapeByType": {
|
||||||
|
"*": { "base": "orekiwoofsbeehives:item/beehiveframe", "rotateY": 90 }
|
||||||
|
},
|
||||||
|
"texturesByType": {
|
||||||
|
"beehiveframe-empty": {
|
||||||
|
"frame": { "base": "game:block/wood-generic" },
|
||||||
|
"center": { "base": "game:block/linen" }
|
||||||
|
},
|
||||||
|
"beehiveframe-filled": {
|
||||||
|
"frame": { "base": "game:block/wood-generic" },
|
||||||
|
"center": { "base": "game:block/resin" }
|
||||||
|
},
|
||||||
|
"beehiveframe-filled-feed": {
|
||||||
|
"frame": { "base": "game:block/wood-generic" },
|
||||||
|
"center": { "base": "game:block/food/grain/flax" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"blockmaterial": "Wood",
|
||||||
|
"replaceable": 700,
|
||||||
|
"resistance": 1.5,
|
||||||
|
"lightAbsorption": 0,
|
||||||
|
"sounds": {
|
||||||
|
"walk": "walk/wood"
|
||||||
|
},
|
||||||
|
"collisionbox": { "x1": 0.125, "y1": 0, "z1": 0.125, "x2": 0.875, "y2": 0.0625, "z2": 0.875 },
|
||||||
|
"selectionbox": { "x1": 0.125, "y1": 0, "z1": 0.125, "x2": 0.875, "y2": 0.0625, "z2": 0.875 },
|
||||||
|
"sideopaque": {
|
||||||
|
"all": false
|
||||||
|
},
|
||||||
|
"sidesolid": {
|
||||||
|
"all": false
|
||||||
|
},
|
||||||
|
"heldTpIdleAnimation": "holdbothhandslarge",
|
||||||
|
"heldTpUseAnimation": "twohandplaceblock",
|
||||||
|
"guiTransform": {
|
||||||
|
"translation": { "x": 0, "y": 3, "z": 0 },
|
||||||
|
"origin": { "x": 0.5, "y": 0.0625, "z": 0.5 },
|
||||||
|
"scale": 1.33
|
||||||
|
},
|
||||||
|
"tpHandTransform": {
|
||||||
|
"translation": { "x": -1, "y": -0.6, "z": -1.05 },
|
||||||
|
"rotation": { "x": -87, "y": 9, "z": 4 },
|
||||||
|
"origin": { "x": 0.5, "y": 0.125, "z": 0.5 },
|
||||||
|
"scale": 0.5
|
||||||
|
},
|
||||||
|
"fpHandTransform": {
|
||||||
|
"translation": { "x": -0.3, "y": 0, "z": 0.5 },
|
||||||
|
"rotation": { "x": 16, "y": -51, "z": 91 },
|
||||||
|
"origin": { "x": 0.5, "y": 0.125, "z": 0.5 }
|
||||||
|
},
|
||||||
|
"groundTransform": {
|
||||||
|
"translation": { "x": 0, "y": 0, "z": 0 },
|
||||||
|
"rotation": { "x": 90, "y": -45, "z": 90 },
|
||||||
|
"origin": { "x": 0.5, "y": 0, "z": 0.5 },
|
||||||
|
"scale": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"code": "beehiveroof",
|
||||||
|
"class": "Block",
|
||||||
|
"variantgroups": [
|
||||||
|
{ "code": "side", "loadFromProperties": "abstract/horizontalorientation" }
|
||||||
|
],
|
||||||
|
"behaviors": [{"name": "HorizontalOrientable", "properties": { "dropBlockFace": "north", "orientate": true }}],
|
||||||
|
"blockmaterial": "Wood",
|
||||||
|
"creativeinventory": {
|
||||||
|
"general": ["*-north"],
|
||||||
|
"decorative": ["*-north"]
|
||||||
|
},
|
||||||
|
"lightAbsorption": 0,
|
||||||
|
"drop": {
|
||||||
|
"code": "orekiwoofsbeehives:beehiveroof-north",
|
||||||
|
"type": "block"
|
||||||
|
},
|
||||||
|
"resistance": 2.0,
|
||||||
|
"collisionbox": { "x1": 0, "y1": 0, "z1": 0, "x2": 1, "y2": 0.25, "z2": 1 },
|
||||||
|
"selectionbox": { "x1": 0, "y1": 0, "z1": 0, "x2": 1, "y2": 0.25, "z2": 1 },
|
||||||
|
"shapebytype": {
|
||||||
|
"*-north": { "base": "orekiwoofsbeehives:block/beehiveroof", "rotateY": 0 },
|
||||||
|
"*-east": { "base": "orekiwoofsbeehives:block/beehiveroof", "rotateY": 270 },
|
||||||
|
"*-south": { "base": "orekiwoofsbeehives:block/beehiveroof", "rotateY": 180 },
|
||||||
|
"*-west": { "base": "orekiwoofsbeehives:block/beehiveroof", "rotateY": 90 }
|
||||||
|
},
|
||||||
|
"textures": {
|
||||||
|
"roof-plank": { "base": "game:block/wood/planks/oak1" }
|
||||||
|
},
|
||||||
|
"sideopaque": {
|
||||||
|
"all": false
|
||||||
|
},
|
||||||
|
"sidesolid": {
|
||||||
|
"all": false
|
||||||
|
},
|
||||||
|
"sounds": {
|
||||||
|
"place": "game:block/planks",
|
||||||
|
"break": "game:block/planks",
|
||||||
|
"hit": "game:block/planks"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"code": "beehivestand",
|
||||||
|
"class": "Block",
|
||||||
|
"variantgroups": [
|
||||||
|
{ "code": "side", "loadFromProperties": "abstract/horizontalorientation" }
|
||||||
|
],
|
||||||
|
"behaviors": [{"name": "HorizontalOrientable", "properties": { "orientate": true }}],
|
||||||
|
"blockmaterial": "Wood",
|
||||||
|
"creativeinventory": {
|
||||||
|
"general": ["*-north"],
|
||||||
|
"decorative": ["*-north"]
|
||||||
|
},
|
||||||
|
"lightAbsorption": 0,
|
||||||
|
"drop": {
|
||||||
|
"code": "orekiwoofsbeehives:beehivestand-north",
|
||||||
|
"type": "block"
|
||||||
|
},
|
||||||
|
"resistance": 2.0,
|
||||||
|
"collisionbox": { "x1": 0, "y1": 0, "z1": 0, "x2": 1, "y2": 1, "z2": 1 },
|
||||||
|
"selectionbox": { "x1": 0, "y1": 0, "z1": 0, "x2": 1, "y2": 1, "z2": 1 },
|
||||||
|
"shapebytype": {
|
||||||
|
"*-north": { "base": "orekiwoofsbeehives:block/beehivestand", "rotateY": 0 },
|
||||||
|
"*-east": { "base": "orekiwoofsbeehives:block/beehivestand", "rotateY": 270 },
|
||||||
|
"*-south": { "base": "orekiwoofsbeehives:block/beehivestand", "rotateY": 180 },
|
||||||
|
"*-west": { "base": "orekiwoofsbeehives:block/beehivestand", "rotateY": 90 }
|
||||||
|
},
|
||||||
|
"textures": {
|
||||||
|
"all": {
|
||||||
|
"base": "game:block/wood/henbox/sides"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sideopaque": {
|
||||||
|
"all": false
|
||||||
|
},
|
||||||
|
"sidesolid": {
|
||||||
|
"all": false
|
||||||
|
},
|
||||||
|
"sounds": {
|
||||||
|
"place": "game:block/planks",
|
||||||
|
"break": "game:block/planks",
|
||||||
|
"hit": "game:block/planks"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
{
|
||||||
|
"code": "beeswarm",
|
||||||
|
"class": "BlockBeeSwarm",
|
||||||
|
"entityClass": "BlockEntityBeeSwarm",
|
||||||
|
"variantgroups": [
|
||||||
|
{ "code": "side", "states": ["north", "east", "south", "west", "down", "up"] }
|
||||||
|
],
|
||||||
|
"blockmaterial": "Leaves",
|
||||||
|
"creativeinventory": {
|
||||||
|
"general": []
|
||||||
|
},
|
||||||
|
"replaceable": 0,
|
||||||
|
"resistance": 0.1,
|
||||||
|
"lightAbsorption": 0,
|
||||||
|
"collisionbox": { "x1": 0.2, "y1": 0.2, "z1": 0.2, "x2": 0.8, "y2": 0.8, "z2": 0.8 },
|
||||||
|
"selectionbox": { "x1": 0.2, "y1": 0.2, "z1": 0.2, "x2": 0.8, "y2": 0.8, "z2": 0.8 },
|
||||||
|
"shapebytype": {
|
||||||
|
"*-north": { "base": "orekiwoofsbeehives:block/beeswarm", "rotateY": 0 },
|
||||||
|
"*-east": { "base": "orekiwoofsbeehives:block/beeswarm", "rotateY": 270 },
|
||||||
|
"*-south": { "base": "orekiwoofsbeehives:block/beeswarm", "rotateY": 180 },
|
||||||
|
"*-west": { "base": "orekiwoofsbeehives:block/beeswarm", "rotateY": 90 },
|
||||||
|
"*-down": { "base": "orekiwoofsbeehives:block/beeswarm", "rotateX": 270, "rotateY": 0 },
|
||||||
|
"*-up": { "base": "orekiwoofsbeehives:block/beeswarm", "rotateX": 90, "rotateY": 0 }
|
||||||
|
},
|
||||||
|
"textures": {
|
||||||
|
"swarm": {
|
||||||
|
"base": "game:block/soil/fertcompost"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sideopaque": {
|
||||||
|
"all": false
|
||||||
|
},
|
||||||
|
"sidesolid": {
|
||||||
|
"all": false
|
||||||
|
},
|
||||||
|
"sounds": {
|
||||||
|
"break": "game:block/leaves",
|
||||||
|
"hit": "game:block/leaves",
|
||||||
|
"ambient": "game:creature/beehive"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,430 @@
|
|||||||
|
{
|
||||||
|
"version": 5,
|
||||||
|
"file": "OrekiWoofsBeehives.json",
|
||||||
|
"patches": {},
|
||||||
|
"settings": [
|
||||||
|
{
|
||||||
|
"type": "separator",
|
||||||
|
"title": "Client-side"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "InformationVerbosity",
|
||||||
|
"comment": "config-desc-InformationVerbosity",
|
||||||
|
"type": "int",
|
||||||
|
"default": 1,
|
||||||
|
"range": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 4
|
||||||
|
},
|
||||||
|
"clientSide": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "DisableServerRecommended",
|
||||||
|
"comment": "config-desc-DisableServerRecommended",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"clientSide": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BeehiveAmbientVolume",
|
||||||
|
"comment": "config-desc-BeehiveAmbientVolume",
|
||||||
|
"type": "float",
|
||||||
|
"default": 0.5,
|
||||||
|
"range": {
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 1.0
|
||||||
|
},
|
||||||
|
"clientSide": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "separator",
|
||||||
|
"title": "Server-side"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "InformationVerbosityServerSideType",
|
||||||
|
"comment": "config-desc-InformationVerbosityServerSideType",
|
||||||
|
"type": "string",
|
||||||
|
"default": "ClientSide",
|
||||||
|
"values": [
|
||||||
|
"ClientSide",
|
||||||
|
"ServerRecommended",
|
||||||
|
"ServerForced"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "InformationVerbosityServer",
|
||||||
|
"comment": "config-desc-InformationVerbosityServer",
|
||||||
|
"type": "int",
|
||||||
|
"default": 1,
|
||||||
|
"range": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "separator",
|
||||||
|
"title": "Beehive Settings"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BeehiveRadius",
|
||||||
|
"comment": "config-desc-BeehiveRadius",
|
||||||
|
"type": "int",
|
||||||
|
"default": 35,
|
||||||
|
"range": {
|
||||||
|
"min": 5,
|
||||||
|
"max": 70
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "YieldBoost",
|
||||||
|
"comment": "config-desc-YieldBoost",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "SpeedBoost",
|
||||||
|
"comment": "config-desc-SpeedBoost",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "GrowthSpeedBonus",
|
||||||
|
"comment": "config-desc-GrowthSpeedBonus",
|
||||||
|
"type": "float",
|
||||||
|
"default": 0.2,
|
||||||
|
"range": {
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 1.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "YieldMultiplier",
|
||||||
|
"comment": "config-desc-YieldMultiplier",
|
||||||
|
"type": "float",
|
||||||
|
"default": 1.2,
|
||||||
|
"range": {
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 3.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "PlantsFullBoostDistanceToHive",
|
||||||
|
"comment": "config-desc-PlantsFullBoostDistanceToHive",
|
||||||
|
"type": "int",
|
||||||
|
"default": 20,
|
||||||
|
"range": {
|
||||||
|
"min": 5,
|
||||||
|
"max": 50
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "MinBeesForPlantBoost",
|
||||||
|
"comment": "config-desc-MinBeesForPlantBoost",
|
||||||
|
"type": "int",
|
||||||
|
"default": 1000,
|
||||||
|
"range": {
|
||||||
|
"min": 100,
|
||||||
|
"max": 5000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BoostCalculation",
|
||||||
|
"comment": "config-desc-BoostCalculation",
|
||||||
|
"type": "string",
|
||||||
|
"default": "NormalizedLogk10Population",
|
||||||
|
"values": [
|
||||||
|
"Full",
|
||||||
|
"LinearPopulation",
|
||||||
|
"NormalizedLogk10Population"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "PopulationPercentForMaxBoost",
|
||||||
|
"comment": "config-desc-PopulationPercentForMaxBoost",
|
||||||
|
"type": "float",
|
||||||
|
"default": 100,
|
||||||
|
"range": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "MultipleBeehivesBoostCombination",
|
||||||
|
"comment": "config-desc-MultipleBeehivesBoostCombination",
|
||||||
|
"type": "string",
|
||||||
|
"default": "Max",
|
||||||
|
"values": [
|
||||||
|
"Additive",
|
||||||
|
"Max"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "separator",
|
||||||
|
"title": "Bee Population"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "MaxBeePopulation",
|
||||||
|
"comment": "config-desc-MaxBeePopulation",
|
||||||
|
"type": "int",
|
||||||
|
"default": 50000,
|
||||||
|
"range": {
|
||||||
|
"min": 1000,
|
||||||
|
"max": 200000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "InitialBeePopulation",
|
||||||
|
"comment": "config-desc-InitialBeePopulation",
|
||||||
|
"type": "int",
|
||||||
|
"default": 5000,
|
||||||
|
"range": {
|
||||||
|
"min": 100,
|
||||||
|
"max": 50000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BeehiveConsideredEmptyBelowPopulation",
|
||||||
|
"comment": "config-desc-BeehiveConsideredEmptyBelowPopulation",
|
||||||
|
"type": "int",
|
||||||
|
"default": 500,
|
||||||
|
"range": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 200000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BaseDeathsPerDay",
|
||||||
|
"comment": "config-desc-BaseDeathsPerDay",
|
||||||
|
"type": "int",
|
||||||
|
"default": 100,
|
||||||
|
"range": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 1000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "FlowerThreshold",
|
||||||
|
"comment": "config-desc-FlowerThreshold",
|
||||||
|
"type": "int",
|
||||||
|
"default": 10,
|
||||||
|
"range": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 50
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "DeathPerMissingFlower",
|
||||||
|
"comment": "config-desc-DeathPerMissingFlower",
|
||||||
|
"type": "int",
|
||||||
|
"default": 100,
|
||||||
|
"range": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 500
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "DoublingTimeDays",
|
||||||
|
"comment": "config-desc-DoublingTimeDays",
|
||||||
|
"type": "float",
|
||||||
|
"default": 9.0,
|
||||||
|
"range": {
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 30.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BonusGrowthPerFilledFrame",
|
||||||
|
"comment": "config-desc-BonusGrowthPerFilledFrame",
|
||||||
|
"type": "int",
|
||||||
|
"default": 100,
|
||||||
|
"range": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 2000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "separator",
|
||||||
|
"title": "Swarming"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "EnableSwarms",
|
||||||
|
"comment": "config-desc-EnableSwarms",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "SwarmSettingAfterPlacing",
|
||||||
|
"comment": "config-desc-SwarmSettingAfterPlacing",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "PreSwarmDurationHours",
|
||||||
|
"comment": "config-desc-PreSwarmDurationHours",
|
||||||
|
"type": "int",
|
||||||
|
"default": 40,
|
||||||
|
"range": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 240
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "PopulationPercentRequirementForSwarm",
|
||||||
|
"comment": "config-desc-PopulationPercentRequirementForSwarm",
|
||||||
|
"type": "int",
|
||||||
|
"default": 80,
|
||||||
|
"range": {
|
||||||
|
"min": 40,
|
||||||
|
"max": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "SwarmPopulationPercentage",
|
||||||
|
"comment": "config-desc-SwarmPopulationPercentage",
|
||||||
|
"type": "int",
|
||||||
|
"default": 40,
|
||||||
|
"range": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 70
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "SwarmCooldownDays",
|
||||||
|
"comment": "config-desc-SwarmCooldownDays",
|
||||||
|
"type": "int",
|
||||||
|
"default": 4,
|
||||||
|
"range": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 30
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "SwarmReturnToOriginOnFailedMigrationPercent",
|
||||||
|
"comment": "config-desc-SwarmReturnToOriginOnFailedMigrationPercent",
|
||||||
|
"type": "int",
|
||||||
|
"default": 20,
|
||||||
|
"range": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "separator",
|
||||||
|
"title": "Honey Production"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ReferenceFlowers",
|
||||||
|
"comment": "config-desc-ReferenceFlowers",
|
||||||
|
"type": "float",
|
||||||
|
"default": 10.0,
|
||||||
|
"range": {
|
||||||
|
"min": 1.0,
|
||||||
|
"max": 50.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "ReferenceBees",
|
||||||
|
"comment": "config-desc-ReferenceBees",
|
||||||
|
"type": "float",
|
||||||
|
"default": 25000.0,
|
||||||
|
"range": {
|
||||||
|
"min": 1000.0,
|
||||||
|
"max": 50000.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "MaxFlowersForHoneyProduction",
|
||||||
|
"comment": "config-desc-MaxFlowersForHoneyProduction",
|
||||||
|
"type": "int",
|
||||||
|
"default": 25,
|
||||||
|
"range": {
|
||||||
|
"min": 1,
|
||||||
|
"max": 100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "separator",
|
||||||
|
"title": "Winter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "WinterHardMode",
|
||||||
|
"comment": "config-desc-WinterHardMode",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "WinterDailyBeeDeathsWithoutFood",
|
||||||
|
"comment": "config-desc-WinterDailyBeeDeathsWithoutFood",
|
||||||
|
"type": "int",
|
||||||
|
"default": 300,
|
||||||
|
"range": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 50000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "WinterFoodConsumptionMultiplier",
|
||||||
|
"comment": "config-desc-WinterFoodConsumptionMultiplier",
|
||||||
|
"type": "float",
|
||||||
|
"default": 0.2,
|
||||||
|
"range": {
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 1.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "separator",
|
||||||
|
"title": "Weather Effects"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "MinTemperatureGrowth",
|
||||||
|
"comment": "config-desc-MinTemperatureGrowth",
|
||||||
|
"type": "float",
|
||||||
|
"default": 0.0,
|
||||||
|
"range": {
|
||||||
|
"min": -20.0,
|
||||||
|
"max": 20.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "MaxTemperatureGrowth",
|
||||||
|
"comment": "config-desc-MaxTemperatureGrowth",
|
||||||
|
"type": "float",
|
||||||
|
"default": 10.0,
|
||||||
|
"range": {
|
||||||
|
"min": 0.0,
|
||||||
|
"max": 40.0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "GreenhouseAffectsBeehive",
|
||||||
|
"comment": "config-desc-GreenhouseAffectsBeehive",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "separator",
|
||||||
|
"title": "Particles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BeesPerParticle",
|
||||||
|
"comment": "config-desc-BeesPerParticle",
|
||||||
|
"type": "int",
|
||||||
|
"default": 1000,
|
||||||
|
"range": {
|
||||||
|
"min": 100,
|
||||||
|
"max": 5000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"code": "BeehiveAlwaysSpawnNumberOfBees",
|
||||||
|
"comment": "config-desc-BeehiveAlwaysSpawnNumberOfBees",
|
||||||
|
"type": "int",
|
||||||
|
"default": 0,
|
||||||
|
"range": {
|
||||||
|
"min": 0,
|
||||||
|
"max": 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
52
OrekiWoofsBeehives/assets/orekiwoofsbeehives/lang/cs.json
Normal file
52
OrekiWoofsBeehives/assets/orekiwoofsbeehives/lang/cs.json
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"block-beehive-*": "Včelí úl",
|
||||||
|
"block-beehivestand-*": "Stojan na úl",
|
||||||
|
"block-beehiveroof-*": "Střecha úlu",
|
||||||
|
"block-beehiveframe-empty": "Prázdný rámek do úlu",
|
||||||
|
"block-beehiveframe-filled": "Naplněný rámek",
|
||||||
|
"block-beehiveframe-filled-feed": "Rámek s krmivem",
|
||||||
|
"beehiveframe-feed-percent": ">>>percent<<<%",
|
||||||
|
"beehive-info-no-bees": "Žádné včely. Použij osídlený koš (skep) k osídlení úlu.",
|
||||||
|
"beehive-info-frames-boosting": "Uložený med podporuje růst populace.",
|
||||||
|
"beehive-info-frames-sacrificing": "Včely obětují polovinu růstu populace pro výrobu medu.",
|
||||||
|
"beehive-info-population": "Populace včel: >>>beePopulation<<< / >>>maxBeePopulation<<< (>>>beeProductionMultiplier<<<x produkce medu)",
|
||||||
|
"beehive-info-population-v2": "Populace včel: >>>beePopulation<<< / >>>maxBeePopulation<<< (>>>beeProductionMultiplier<<<x produkce medu)",
|
||||||
|
"beehive-info-population-none": "Populace včel: Žádná",
|
||||||
|
"beehive-info-population-low": "Populace včel: Nízká",
|
||||||
|
"beehive-info-population-medium": "Populace včel: Střední",
|
||||||
|
"beehive-info-population-high": "Populace včel: Vysoká",
|
||||||
|
"beehive-info-population-veryhigh": "Populace včel: Velmi vysoká",
|
||||||
|
"beehive-info-flowers": "Květiny v okolí: >>>flowersAround<<<, Plodiny: >>>cropsAround<<<, Efektivně: >>>effectiveFlowers<<< (>>>flowerProductionMultiplier<<<x produkce medu)",
|
||||||
|
"beehive-info-flowers-simple": "Květiny v okolí: >>>flowersAround<<<, Plodiny: >>>cropsAround<<<",
|
||||||
|
"beehive-info-flowers-scanning-suffix": "(oblast se stále prozkoumává)",
|
||||||
|
"beehive-info-frames-mixed": "Rámky: >>>filledFrames<<< naplněných (<font color=\"#00aa00\">+>>>filledFrameBonus<<<</font> včel/den), >>>emptyFrames<<< prázdných (<font color=\"#994d00\">-0.5x</font> včel/den)",
|
||||||
|
"beehive-info-frames-filled": "Rámky: >>>filledFrames<<< naplněných (<font color=\"#00aa00\">+>>>filledFrameBonus<<<</font> včel/den)",
|
||||||
|
"beehive-info-frames-empty": "Rámky: >>>emptyFrames<<< prázdných (<font color=\"#ffff00\">-0.5x</font> včel/den)",
|
||||||
|
"beehive-info-frames-total": "Rámky: >>>totalFrames<<<",
|
||||||
|
"beehive-info-progress-filling": "Postup plnění rámku: >>>honeyProgress<<<% (naplní se za >>>daysToFill<<< dní)",
|
||||||
|
"beehive-info-progress-noproduction": "Postup plnění rámku: >>>honeyProgress<<<% (žádná produkce – potřeba včely a květiny)",
|
||||||
|
"beehive-info-progress-v1-noproduction": "Žádná produkce medu",
|
||||||
|
"beehive-info-progress-v1-lessthanday": "Naplněný rámek bude vyroben za méně než den",
|
||||||
|
"beehive-info-progress-v1-days": "Naplněný rámek bude vyroben za >>>days<<< dní",
|
||||||
|
"beehive-info-all-filled": "Všechny rámky jsou naplněné! Přidej prázdné rámky pro pokračování produkce.",
|
||||||
|
"beehive-info-no-frames": "V úlu nejsou žádné rámky. Přidej prázdné rámky pro zahájení produkce.",
|
||||||
|
"beehive-info-temperature": "Teplota: >>>temperature<<<°C (>>>multiplier<<<x efektivita a změna populace)",
|
||||||
|
"beehive-info-temperature-cold": "Nízká teplota způsobuje, že včely zůstávají více uvnitř.",
|
||||||
|
"beehive-info-temperature-overwintering": "Včely přezimují.",
|
||||||
|
"beehive-info-population-change": "Změna populace: >>>changeSign<<<>>>netChange<<</den (růst: +>>>dailyGrowth<<<, úbytek: ->>>dailyDeaths<<<)",
|
||||||
|
"beehive-info-population-status-decreasing": "Populace klesá",
|
||||||
|
"beehive-info-population-status-stagnant": "Populace stagnuje",
|
||||||
|
"beehive-info-population-status-slowgrowth": "Populace pomalu roste",
|
||||||
|
"beehive-info-population-status-growing": "Populace roste",
|
||||||
|
"beehive-info-honey-production": "Produkce medu: >>>framesPerDay<<<x (>>>daysPerFrame<<< dní na rámek)",
|
||||||
|
"beehive-info-honey-production-simple": "Produkce medu: >>>framesPerDay<<<x",
|
||||||
|
"beehive-info-feed-current": "Aktuální rámek s krmivem: >>>feedRemainingPercent<<<% (>>>daysPerFrame<<< dní na rámek)",
|
||||||
|
"blockinfo-in-range-beehive": "V dosahu {p0:# úlů|# úlu|# úlů}",
|
||||||
|
"blockinfo-growth-speed-bonus": "{0}% rychlost růstu",
|
||||||
|
"blockinfo-yield-bonus": "{0}% výnos",
|
||||||
|
"blockinfo-will-be-pollinated": "Bude opyleno za {p0:# další fáze růstu|# další fázi růstu|# další fáze růstu}",
|
||||||
|
"blockinfo-pollinated": "Opyleno",
|
||||||
|
"beehiveScoutingProgress": "Postup průzkumu",
|
||||||
|
"handbook-beehive": "- <a href=\"handbook://block-orekiwoofsbeehives:beehiveroof-north\">Střecha</a><br>- <a href=\"handbook://block-orekiwoofsbeehives:beehivestand-north\">Stojan</a>",
|
||||||
|
"handbook-beehive-title": "Dekorace"
|
||||||
|
}
|
||||||
137
OrekiWoofsBeehives/assets/orekiwoofsbeehives/lang/en.json
Normal file
137
OrekiWoofsBeehives/assets/orekiwoofsbeehives/lang/en.json
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
{
|
||||||
|
"block-beehive-*": "Beehive",
|
||||||
|
"block-beehivestand-*": "Beehive stand",
|
||||||
|
"block-beehiveroof-*": "Beehive roof",
|
||||||
|
"block-beehiveframe-empty": "Empty beehive frame",
|
||||||
|
"block-beehiveframe-filled": "Filled beehive frame",
|
||||||
|
"block-beehiveframe-filled-feed": "Beehive frame with feed",
|
||||||
|
"block-beeswarm-*": "Bee swarm",
|
||||||
|
"beehiveframe-feed-percent": ">>>percent<<<%",
|
||||||
|
"beehive-info-no-bees": "No bees. Use a populated skep to populate the beehive.",
|
||||||
|
"beehive-info-no-bees-use-skep-or-wait-for-swarm": "No bees. Use a populated skep to populate the beehive, or wait for a swarm.",
|
||||||
|
"beehive-info-frames-boosting": "The stored honey is boosting population growth.",
|
||||||
|
"beehive-info-frames-sacrificing": "The bees are sacrificing half their population growth to make honey.",
|
||||||
|
"beehive-info-population": "Bee population: >>>beePopulation<<< / >>>maxBeePopulation<<< (>>>beeProductionMultiplier<<<x honey production)",
|
||||||
|
"beehive-info-population-v2": "Bee population: >>>beePopulation<<< / >>>maxBeePopulation<<< (>>>beeProductionMultiplier<<<x honey production)",
|
||||||
|
"beehive-info-population-none": "Bee population: None",
|
||||||
|
"beehive-info-population-low": "Bee population: Low",
|
||||||
|
"beehive-info-population-medium": "Bee population: Medium",
|
||||||
|
"beehive-info-population-high": "Bee population: High",
|
||||||
|
"beehive-info-population-veryhigh": "Bee population: Very high",
|
||||||
|
"beehive-info-flowers": "Flowers around: >>>flowersAround<<<, Crops: >>>cropsAround<<<, Effectively: >>>effectiveFlowers<<< (>>>flowerProductionMultiplier<<<x honey production)",
|
||||||
|
"beehive-info-flowers-simple": "Flowers around: >>>flowersAround<<<, Crops: >>>cropsAround<<<",
|
||||||
|
"beehive-info-flowers-scanning-suffix": "still scouting the area",
|
||||||
|
"beehive-info-flowers-warning-causing-deaths": "Lack of flowers is causing the population to drop. Plant >>>flowersNeeded<<< more flowers nearby.",
|
||||||
|
"beehive-info-flowers-warning-suboptimal": "The beehive would be more productive if there were more flowers nearby.",
|
||||||
|
"beehive-info-frames-mixed": "Frames: >>>filledFrames<<< filled (<font color=\"#00aa00\">+>>>filledFrameBonus<<<</font> bees/day), >>>emptyFrames<<< empty (<font color=\"#994d00\">-0.5x</font> bees/day)",
|
||||||
|
"beehive-info-frames-filled": "Frames: >>>filledFrames<<< filled (<font color=\"#00aa00\">+>>>filledFrameBonus<<<</font> bees/day)",
|
||||||
|
"beehive-info-frames-empty": "Frames: >>>emptyFrames<<< empty (<font color=\"#ffff00\">-0.5x</font> bees/day)",
|
||||||
|
"beehive-info-frames-total": "Frames: >>>totalFrames<<<",
|
||||||
|
"beehive-info-progress-filling": "Frame progress: >>>honeyProgress<<<% (fills in >>>daysToFill<<< days)",
|
||||||
|
"beehive-info-progress-noproduction": "Frame progress: >>>honeyProgress<<<% (no production - need bees and flowers)",
|
||||||
|
"beehive-info-progress-v1-noproduction": "No honey production",
|
||||||
|
"beehive-info-progress-v1-lessthanday": "Will produce a filled frame in less than a day",
|
||||||
|
"beehive-info-progress-v1-days": "Will produce a filled frame in >>>days<<< days",
|
||||||
|
"beehive-info-all-filled": "All frames filled! Add empty frames to continue production.",
|
||||||
|
"beehive-info-no-frames": "No frames in hive. Add empty frames to start production.",
|
||||||
|
"beehive-info-temperature": "Temperature: >>>temperature<<<°C (>>>multiplier<<<x efficiency and population change)",
|
||||||
|
"beehive-info-temperature-cold": "The cold temperature is making the bees stay inside more.",
|
||||||
|
"beehive-info-temperature-overwintering": "The bees are overwintering.",
|
||||||
|
"beehive-info-winter-starving": "Bees need additional food in the winter. Add a feed frame to keep them healthy.",
|
||||||
|
"beehive-info-population-change": "Population change: >>>changeSign<<<>>>netChange<<</day (growth: +>>>dailyGrowth<<<, decrease: ->>>dailyDeaths<<<)",
|
||||||
|
"beehive-info-population-status-decreasing": "Decreasing population",
|
||||||
|
"beehive-info-population-status-stagnant": "Stagnant population",
|
||||||
|
"beehive-info-population-status-slowgrowth": "Slowly increasing population",
|
||||||
|
"beehive-info-population-status-growing": "Growing population",
|
||||||
|
"beehive-info-incoming-swarm": "A swarm is moving into this beehive.",
|
||||||
|
"beehive-info-source-swarm-forming": "Some of the bees are forming a swarm nearby.",
|
||||||
|
"beehive-info-recently-swarmed": "This beehive has swarmed in the last {p0:# days|# day|# days}.",
|
||||||
|
"beehive-info-ready-waiting-morning": "Ready to swarm, waiting for morning.",
|
||||||
|
"beehive-info-ready-to-swarm": "Ready to swarm.",
|
||||||
|
"beehive-info-honey-production": "Honey production: >>>framesPerDay<<<x (>>>daysPerFrame<<< days per frame)",
|
||||||
|
"beehive-info-honey-production-simple": "Honey production: >>>framesPerDay<<<x",
|
||||||
|
"beehive-info-feed-current": "Current frame with feed: >>>feedRemainingPercent<<<% (>>>daysPerFrame<<< days per frame)",
|
||||||
|
"blockinfo-in-range-beehive": "In range of {p0:# beehives|# beehive|# beehives}",
|
||||||
|
"blockinfo-as-a-plant": "as a {0}",
|
||||||
|
"blockinfo-flower": "flower",
|
||||||
|
"blockinfo-crop": "crop",
|
||||||
|
"blockinfo-growth-speed-bonus": "{0}% growth speed",
|
||||||
|
"blockinfo-yield-bonus": "{0}% yield",
|
||||||
|
"blockinfo-crop-boost-effectiveness": "Crop boost effectiveness: {0}",
|
||||||
|
"blockinfo-crop-boost-result-yield": "+{0}% yield",
|
||||||
|
"blockinfo-crop-boost-result-speed": "+{0}% speed",
|
||||||
|
"blockinfo-crop-boost-level-low": "Low",
|
||||||
|
"blockinfo-crop-boost-level-medium": "Medium",
|
||||||
|
"blockinfo-crop-boost-level-high": "High",
|
||||||
|
"blockinfo-crop-boost-population-too-low": "Population too low for a crop boost",
|
||||||
|
"blockinfo-will-be-pollinated": "Will be pollinated in {p0:# more growth stages|# more growth stage|# more growth stages}",
|
||||||
|
"blockinfo-pollinated": "Pollinated",
|
||||||
|
"beehiveScoutingProgress": "Scouting progress",
|
||||||
|
"beeswarm-info-population": "Swarm population: {0}",
|
||||||
|
"beeswarm-info-eligible-targets": "Eligible new homes in the area: {0}",
|
||||||
|
"beeswarm-phase-forming": "The swarm is forming ({0})",
|
||||||
|
"beeswarm-phase-scouting": "The swarm is scouting for a new home ({0})",
|
||||||
|
"beeswarm-phase-moving": "The swarm is moving to a new home ({0})",
|
||||||
|
"beeswarm-hours-left": "{0} hours left",
|
||||||
|
"beeswarm-less-than-hour-left": "less than 1 hour left",
|
||||||
|
|
||||||
|
"handbook-beehive": "- <a href=\"handbook://block-orekiwoofsbeehives:beehiveroof-north\">Roof</a><br>- <a href=\"handbook://block-orekiwoofsbeehives:beehivestand-north\">Stand</a>",
|
||||||
|
"handbook-beehive-title": "Decorations",
|
||||||
|
|
||||||
|
"config-desc-InformationVerbosity": "How much information should be shown when hovering over a beehive block.",
|
||||||
|
"config-desc-DisableServerRecommended": "If true, ignores InformationVerbosityServer when InformationVerbosityServerSideType == ServerRecommended. Has no effect when server mode is ServerForced.",
|
||||||
|
"config-desc-BeehiveAmbientVolume": "Ambient beehive volume. Note that this may take a while to apply, because it works from the next \"sound reload\".",
|
||||||
|
"config-desc-InformationVerbosityServerSideType": "Controls whether InformationVerbosity is client-side only, server-recommended, or server-forced.",
|
||||||
|
"config-desc-InformationVerbosityServer": "Server-side verbosity value used when InformationVerbosityServerSideType is ServerRecommended or ServerForced.",
|
||||||
|
"config-desc-BeehiveRadius": "The radius within which beehives affect crops, and within which the bees will visit flowers.",
|
||||||
|
"config-desc-YieldBoost": "Whether beehive proximity gives crops a yield bonus.",
|
||||||
|
"config-desc-SpeedBoost": "Whether beehive proximity gives crops a growth speed bonus.",
|
||||||
|
"config-desc-GrowthSpeedBonus": "Growth speed bonus when in beehive range (0.2 = up to 20% faster). Works only with SpeedBoost enabled.",
|
||||||
|
"config-desc-YieldMultiplier": "Crop yield multiplier when in beehive range (1.5 = 150%). Works only with YieldBoost enabled.",
|
||||||
|
"config-desc-PlantsFullBoostDistanceToHive": "Spherical range where crops receive the full growth boost. Beyond this, the boost falls off linearly until BeehiveRadius.",
|
||||||
|
"config-desc-MinBeesForPlantBoost": "Minimum bee population required for a beehive to boost nearby plants.",
|
||||||
|
"config-desc-BoostCalculation": "How bee population scales crop boost once MinBeesForPlantBoost is reached.",
|
||||||
|
"config-desc-PopulationPercentForMaxBoost": "Bee population percentage of MaxBeePopulation needed to reach full population-based crop boost.",
|
||||||
|
"config-desc-MultipleBeehivesBoostCombination": "How crop boost from multiple beehives is combined.",
|
||||||
|
"config-desc-MaxBeePopulation": "Maximum number of bees in a beehive.",
|
||||||
|
"config-desc-InitialBeePopulation": "Bee population added when a populated skep is added to the beehive.",
|
||||||
|
"config-desc-BeehiveConsideredEmptyBelowPopulation": "A beehive is eligible to receive incoming swarms while its population stays below this value.",
|
||||||
|
"config-desc-PopulationPercentRequirementForSwarm": "Population percent of MaxBeePopulation required before a beehive can start a swarm.",
|
||||||
|
"config-desc-SwarmPopulationPercentage": "Percent of a beehive's population that leaves to form a swarm.",
|
||||||
|
"config-desc-SwarmCooldownDays": "Days a beehive must wait after starting a swarm before it can start another one.",
|
||||||
|
"config-desc-SwarmReturnToOriginOnFailedMigrationPercent": "Percent of swarm bees that return to the origin hive when the swarm fails to find a new hive.",
|
||||||
|
"config-desc-BaseDeathsPerDay": "Number of bees that die per day regardless of anything.",
|
||||||
|
"config-desc-FlowerThreshold": "Minimum flowers needed to avoid extra bee deaths.",
|
||||||
|
"config-desc-DeathPerMissingFlower": "Extra bee deaths per day for each flower below threshold.",
|
||||||
|
"config-desc-DoublingTimeDays": "Number of days for bee population to double. Note that this is only used when calculating daily positive growth rate.",
|
||||||
|
"config-desc-BonusGrowthPerFilledFrame": "Extra bees per day per filled frame.",
|
||||||
|
"config-desc-ReferenceFlowers": "Reference flower count for honey production rate (10 flowers + 25000 bees = 1 frame/day).",
|
||||||
|
"config-desc-ReferenceBees": "Reference bee count for honey production rate (10 flowers + 25000 bees = 1 frame/day).",
|
||||||
|
"config-desc-MaxFlowersForHoneyProduction": "Maximum number of flowers counted for honey production calculations.",
|
||||||
|
"config-desc-WinterHardMode": "Enables winter hard mode for additional winter mechanics.",
|
||||||
|
"config-desc-WinterDailyBeeDeathsWithoutFood": "Extra daily bee deaths in winter when there are no filled/feed frames. Works only with WinterHardMode enabled.",
|
||||||
|
"config-desc-WinterFoodConsumptionMultiplier": "Winter food consumption multiplier (0-1). From 10C to 0C this effect ramps up in reverse relative to honey production; below 0C it stays at max. Works only with WinterHardMode enabled.",
|
||||||
|
"config-desc-MinTemperatureGrowth": "Temperature at which bee growth and honey production stops.",
|
||||||
|
"config-desc-MaxTemperatureGrowth": "Temperature at which bee growth and honey production reaches maximum.",
|
||||||
|
"config-desc-GreenhouseAffectsBeehive": "Whether greenhouses give 5C temperature boost to the beehive.",
|
||||||
|
"config-desc-BeesPerParticle": "Number of bees represented by each particle group. If this is 1000, there are 5000 bees in a beehive, then the beehive will spawn at most 5 particles.",
|
||||||
|
"config-desc-BeehiveAlwaysSpawnNumberOfBees": "Will spawn this amount of bees per beehive block, regardless of their population.",
|
||||||
|
"beehive-info-swarms-disabled": "Swarming is disabled on this beehive.",
|
||||||
|
"beehive-info-pre-swarm-building": "Bees are preparing to swarm.",
|
||||||
|
"beehive-info-pre-swarm-progress": "Preparing to swarm ({0})",
|
||||||
|
"beehive-info-pre-swarm-time-lessthanhourleft": "less than an hour left",
|
||||||
|
"beehive-info-pre-swarm-time-hours": "{0} hours left",
|
||||||
|
"beehive-info-pre-swarm-time-1day": "1 day left",
|
||||||
|
"beehive-info-pre-swarm-time-days": "{0} days left",
|
||||||
|
"beehive-info-pre-swarm-paused": "paused due to weather conditions",
|
||||||
|
"blockhelp-beehive-enable-swarm": "Enable swarming",
|
||||||
|
"blockhelp-beehive-disable-swarm": "Disable swarming",
|
||||||
|
"config-desc-EnableSwarms": "Whether beehives can produce swarms.",
|
||||||
|
"config-desc-PreSwarmDurationHours": "Duration in hours for bees to build up their swarm urge (0 to 100%). Progress only increases at MaxTemperatureGrowth or higher; otherwise it decreases.",
|
||||||
|
"config-desc-SwarmSettingAfterPlacing": "Whether a newly placed beehive has swarming enabled or disabled. Can be changed with a wrench.",
|
||||||
|
"setpopulation-desc": "Set bee population of the beehive block you are currently looking at.",
|
||||||
|
"debugunload-desc": "Enable or disable unload catch-up debug messages in server chat.",
|
||||||
|
"plantreg-blockpertick-desc": "Get or set plant registry scan BlocksPerTick (0-1000).",
|
||||||
|
"plantreg-blockpertick-parse-error": "Couldn't parse. Use an integer in range 0-1000.",
|
||||||
|
"plantreg-unavailable": "Plant registry mod system is not available."
|
||||||
|
}
|
||||||
52
OrekiWoofsBeehives/assets/orekiwoofsbeehives/lang/pl.json
Normal file
52
OrekiWoofsBeehives/assets/orekiwoofsbeehives/lang/pl.json
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"block-beehive-*": "Ul",
|
||||||
|
"block-beehivestand-*": "Stojak na ul",
|
||||||
|
"block-beehiveroof-*": "Daszek ulu",
|
||||||
|
"block-beehiveframe-empty": "Pusta ramka ulu",
|
||||||
|
"block-beehiveframe-filled": "Wypełniona ramka ulu",
|
||||||
|
"block-beehiveframe-filled-feed": "Ramka ulu z jedzeniem dla pszczół",
|
||||||
|
"beehiveframe-feed-percent": ">>>percent<<<%",
|
||||||
|
"beehive-info-no-bees": "Brak pszczół. Użyj na ulu zamieszkanej kószki aby wprowadzić populację.",
|
||||||
|
"beehive-info-frames-boosting": "Przechowywany miód wzmacnia wzrost populacji.",
|
||||||
|
"beehive-info-frames-sacrificing": "Pszczoły poświęcają połowę wzrostu populacji, aby produkować miód.",
|
||||||
|
"beehive-info-population": "Populacja pszczół: >>>beePopulation<<< / >>>maxBeePopulation<<< (>>>beeProductionMultiplier<<<x produkcji miodu)",
|
||||||
|
"beehive-info-population-v2": "Populacja pszczół: >>>beePopulation<<< / >>>maxBeePopulation<<< (>>>beeProductionMultiplier<<<x produkcji miodu)",
|
||||||
|
"beehive-info-population-none": "Populacja pszczół: Brak",
|
||||||
|
"beehive-info-population-low": "Populacja pszczół: Niska",
|
||||||
|
"beehive-info-population-medium": "Populacja pszczół: Średnia",
|
||||||
|
"beehive-info-population-high": "Populacja pszczół: Wysoka",
|
||||||
|
"beehive-info-population-veryhigh": "Populacja pszczół: Bardzo wysoka",
|
||||||
|
"beehive-info-flowers": "Kwiaty w okolicy: >>>flowersAround<<<, Uprawy: >>>cropsAround<<<, Efektywnie: >>>effectiveFlowers<<< (>>>flowerProductionMultiplier<<<x produkcji miodu)",
|
||||||
|
"beehive-info-flowers-simple": "Kwiaty w okolicy: >>>flowersAround<<<, Uprawy: >>>cropsAround<<<",
|
||||||
|
"beehive-info-flowers-scanning-suffix": "(wciąż trwa sprawdzanie okolicy)",
|
||||||
|
"beehive-info-frames-mixed": "Wypełnione ramki: >>>filledFrames<<< (<font color=\"#00aa00\">+>>>filledFrameBonus<<<</font> pszczół/dzień), Puste: >>>emptyFrames<<< (<font color=\"#ffff00\">-0.5x</font> pszczół/dzień)",
|
||||||
|
"beehive-info-frames-filled": "Wypełnione ramki: >>>filledFrames<<< (<font color=\"#00aa00\">+>>>filledFrameBonus<<<</font> pszczół/dzień)",
|
||||||
|
"beehive-info-frames-empty": "Puste ramki: >>>emptyFrames<<< (<font color=\"#ffff00\">-0.5x</font> pszczół/dzień)",
|
||||||
|
"beehive-info-frames-total": "Ramki: >>>totalFrames<<<",
|
||||||
|
"beehive-info-progress-filling": "Postęp ramki: >>>honeyProgress<<<% (wypełni się za >>>daysToFill<<< dni)",
|
||||||
|
"beehive-info-progress-noproduction": "Postęp ramki: >>>honeyProgress<<<% (brak produkcji - potrzeba pszczół i kwiatów)",
|
||||||
|
"beehive-info-progress-v1-noproduction": "Brak produkcji miodu",
|
||||||
|
"beehive-info-progress-v1-lessthanday": "Wyprodukuje wypełnioną ramkę za mniej niż dzień",
|
||||||
|
"beehive-info-progress-v1-days": "Wyprodukuje wypełnioną ramkę za >>>days<<< dni",
|
||||||
|
"beehive-info-all-filled": "Wszystkie ramki wypełnione",
|
||||||
|
"beehive-info-no-frames": "Brak ramek w ulu",
|
||||||
|
"beehive-info-temperature": "Temperatura: >>>temperature<<<°C (>>>multiplier<<<x wydajności i zmiany populacji)",
|
||||||
|
"beehive-info-temperature-cold": "Przez zimno pszczoły wolą zostawać w ulu.",
|
||||||
|
"beehive-info-temperature-overwintering": "Pszczoły zimują.",
|
||||||
|
"beehive-info-population-change": "Zmiana populacji: >>>changeSign<<<>>>netChange<<</dzień (wzrost: +>>>dailyGrowth<<<, spadek: ->>>dailyDeaths<<<)",
|
||||||
|
"beehive-info-population-status-decreasing": "Malejąca populacja",
|
||||||
|
"beehive-info-population-status-stagnant": "Stagnująca populacja",
|
||||||
|
"beehive-info-population-status-slowgrowth": "Powoli rosnąca populacja",
|
||||||
|
"beehive-info-population-status-growing": "Rosnąca populacja",
|
||||||
|
"beehive-info-honey-production": "Produkcja miodu: >>>framesPerDay<<<x (>>>daysPerFrame<<< dni na ramkę)",
|
||||||
|
"beehive-info-honey-production-simple": "Produkcja miodu: >>>framesPerDay<<<x",
|
||||||
|
"beehive-info-feed-current": "Aktualna ramka z jedzeniem dla pszczół: >>>feedRemainingPercent<<<% (>>>daysPerFrame<<< dni na ramkę)",
|
||||||
|
"blockinfo-in-range-beehive": "W zasięgu {p0:# uli|# ula|# uli}",
|
||||||
|
"blockinfo-growth-speed-bonus": "{0}% szybkości wzrostu",
|
||||||
|
"blockinfo-yield-bonus": "{0}% plonów",
|
||||||
|
"blockinfo-will-be-pollinated": "Zostanie zapylone za {p0:# etapów wzrostu|# etap wzrostu|# etapy wzrostu}",
|
||||||
|
"blockinfo-pollinated": "Zapylone",
|
||||||
|
"beehiveScoutingProgress": "Rozpoznanie terenu",
|
||||||
|
"handbook-beehive": "- <a href=\"handbook://block-orekiwoofsbeehives:beehiveroof-north\">Daszek</a><br>- <a href=\"handbook://block-orekiwoofsbeehives:beehivestand-north\">Stojak</a>",
|
||||||
|
"handbook-beehive-title": "Urozmaicenia"
|
||||||
|
}
|
||||||
137
OrekiWoofsBeehives/assets/orekiwoofsbeehives/lang/ru.json
Normal file
137
OrekiWoofsBeehives/assets/orekiwoofsbeehives/lang/ru.json
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
{
|
||||||
|
"block-beehive-*": "Улей",
|
||||||
|
"block-beehivestand-*": "Подставка для улья",
|
||||||
|
"block-beehiveroof-*": "Крыша улья",
|
||||||
|
"block-beehiveframe-empty": "Пустая рамка улья",
|
||||||
|
"block-beehiveframe-filled": "Заполненная рамка улья",
|
||||||
|
"block-beehiveframe-filled-feed": "Кормовая рамка улья",
|
||||||
|
"block-beeswarm-*": "Пчелиный рой",
|
||||||
|
"beehiveframe-feed-percent": ">>>percent<<<%",
|
||||||
|
"beehive-info-no-bees": "Пчёл нет. Используйте заселённую корзину, чтобы заселить улей.",
|
||||||
|
"beehive-info-no-bees-use-skep-or-wait-for-swarm": "Пчёл нет. Используйте заселённый плетённый улей или дождитесь роя.",
|
||||||
|
"beehive-info-frames-boosting": "Запасённый мёд ускоряет рост популяции.",
|
||||||
|
"beehive-info-frames-sacrificing": "Пчёлы жертвуют половиной прироста популяции ради производства мёда.",
|
||||||
|
"beehive-info-population": "Популяция пчёл: >>>beePopulation<<< / >>>maxBeePopulation<<< (>>>beeProductionMultiplier<<<x к производству мёда)",
|
||||||
|
"beehive-info-population-v2": "Популяция пчёл: >>>beePopulation<<< / >>>maxBeePopulation<<< (>>>beeProductionMultiplier<<<x к производству мёда)",
|
||||||
|
"beehive-info-population-none": "Популяция пчёл: отсутствует",
|
||||||
|
"beehive-info-population-low": "Популяция пчёл: низкая",
|
||||||
|
"beehive-info-population-medium": "Популяция пчёл: средняя",
|
||||||
|
"beehive-info-population-high": "Популяция пчёл: высокая",
|
||||||
|
"beehive-info-population-veryhigh": "Популяция пчёл: очень высокая",
|
||||||
|
"beehive-info-flowers": "Цветы вокруг: >>>flowersAround<<<, посевы: >>>cropsAround<<<, эффективно: >>>effectiveFlowers<<< (>>>flowerProductionMultiplier<<<x к производству мёда)",
|
||||||
|
"beehive-info-flowers-simple": "Цветы вокруг: >>>flowersAround<<<, посевы: >>>cropsAround<<<",
|
||||||
|
"beehive-info-flowers-scanning-suffix": "(область всё ещё исследуется)",
|
||||||
|
"beehive-info-flowers-warning-causing-deaths": "Нехватка цветов приводит к сокращению популяции. Посадите ещё >>>flowersNeeded<<< цветов поблизости.",
|
||||||
|
"beehive-info-flowers-warning-suboptimal": "Улей был бы продуктивнее, если бы поблизости было больше цветов.",
|
||||||
|
"beehive-info-frames-mixed": "Рамки: >>>filledFrames<<< заполнено (<font color=\"#00aa00\">+>>>filledFrameBonus<<<</font> пчёл/день), >>>emptyFrames<<< пусто (<font color=\"#994d00\">-0.5x</font> пчёл/день)",
|
||||||
|
"beehive-info-frames-filled": "Рамки: >>>filledFrames<<< заполнено (<font color=\"#00aa00\">+>>>filledFrameBonus<<<</font> пчёл/день)",
|
||||||
|
"beehive-info-frames-empty": "Рамки: >>>emptyFrames<<< пусто (<font color=\"#ffff00\">-0.5x</font> пчёл/день)",
|
||||||
|
"beehive-info-frames-total": "Рамки: всего >>>totalFrames<<<",
|
||||||
|
"beehive-info-progress-filling": "Прогресс заполнения рамки: >>>honeyProgress<<<% (заполнится через >>>daysToFill<<< дн.)",
|
||||||
|
"beehive-info-progress-noproduction": "Прогресс заполнения рамки: >>>honeyProgress<<<% (производство отсутствует — нужны пчёлы и цветы)",
|
||||||
|
"beehive-info-progress-v1-noproduction": "Производство мёда отсутствует",
|
||||||
|
"beehive-info-progress-v1-lessthanday": "Заполненная рамка будет получена менее чем за день",
|
||||||
|
"beehive-info-progress-v1-days": "Заполненная рамка будет получена через >>>days<<< дн.",
|
||||||
|
"beehive-info-all-filled": "Все рамки заполнены! Добавьте пустые рамки, чтобы продолжить сбор мёда.",
|
||||||
|
"beehive-info-no-frames": "Нет рамок в улье. Добавьте пустые рамки, чтобы пчёлы начали сбор мёда.",
|
||||||
|
"beehive-info-temperature": "Температура: >>>temperature<<<°C (>>>multiplier<<<x к эффективности и изменению популяции)",
|
||||||
|
"beehive-info-temperature-cold": "Низкая температура заставляет пчёл дольше оставаться внутри.",
|
||||||
|
"beehive-info-temperature-overwintering": "Пчелы перезимовывают.",
|
||||||
|
"beehive-info-winter-starving": "Зимой пчёлам необходим дополнительное питание. Добавьте кормовую рамку, чтобы сохранить их здоровье.",
|
||||||
|
"beehive-info-population-change": "Изменение популяции: >>>changeSign<<<>>>netChange<<</день (рост: +>>>dailyGrowth<<<, спад: - >>>dailyDeaths<<<)",
|
||||||
|
"beehive-info-population-status-decreasing": "Сокращение популяции",
|
||||||
|
"beehive-info-population-status-stagnant": "Популяция стабильна",
|
||||||
|
"beehive-info-population-status-slowgrowth": "Популяция медленно увеличивается",
|
||||||
|
"beehive-info-population-status-growing": "Растущая популяция",
|
||||||
|
"beehive-info-incoming-swarm": "В этот улей вселяется рой.",
|
||||||
|
"beehive-info-source-swarm-forming": "Некоторые пчелы собираются в рой неподалеку.",
|
||||||
|
"beehive-info-recently-swarmed": "Этот улей роился в последние {p0:# дней|# день|# дней}.",
|
||||||
|
"beehive-info-ready-waiting-morning": "Готовые к роению, ожидают утра.",
|
||||||
|
"beehive-info-ready-to-swarm": "Готовы к роению.",
|
||||||
|
"beehive-info-honey-production": "Производство мёда: >>>framesPerDay<<<x (>>>daysPerFrame<<< дн. на рамку)",
|
||||||
|
"beehive-info-honey-production-simple": "Производство мёда: >>>framesPerDay<<<x",
|
||||||
|
"beehive-info-feed-current": "Текущая кормовая рамка улья: >>>feedRemainingPercent<<<% (>>>daysPerFrame<<< дн. на рамку)",
|
||||||
|
"blockinfo-in-range-beehive": "В радиусе действия {p0:# ульев|# улья|# ульев}",
|
||||||
|
"blockinfo-as-a-plant": "как {0}",
|
||||||
|
"blockinfo-flower": "цветок",
|
||||||
|
"blockinfo-crop": "урожай",
|
||||||
|
"blockinfo-growth-speed-bonus": "{0}% к скорости роста",
|
||||||
|
"blockinfo-yield-bonus": "{0}% к урожайности",
|
||||||
|
"blockinfo-crop-boost-effectiveness": "Повышение урожайности: {0}",
|
||||||
|
"blockinfo-crop-boost-result-yield": "+{0}% урожайности",
|
||||||
|
"blockinfo-crop-boost-result-speed": "+{0}% скорости роста",
|
||||||
|
"blockinfo-crop-boost-level-low": "Низкое",
|
||||||
|
"blockinfo-crop-boost-level-medium": "Среднее",
|
||||||
|
"blockinfo-crop-boost-level-high": "Высокое",
|
||||||
|
"blockinfo-crop-boost-population-too-low": "Популяция слишком мала для повышения урожая",
|
||||||
|
"blockinfo-will-be-pollinated": "Будет опылено через {p0:# стадии роста|# стадию роста|# стадии роста}",
|
||||||
|
"blockinfo-pollinated": "Опылено",
|
||||||
|
"beehiveScoutingProgress": "Прогресс разведки",
|
||||||
|
"beeswarm-info-population": "Популяция роя: {0}",
|
||||||
|
"beeswarm-info-eligible-targets": "Подходящие новые места в этом районе: {0}",
|
||||||
|
"beeswarm-phase-forming": "Формируется рой ({0})",
|
||||||
|
"beeswarm-phase-scouting": "Рой ищет новый дом ({0})",
|
||||||
|
"beeswarm-phase-moving": "Рой перелетает в новый дом ({0})",
|
||||||
|
"beeswarm-hours-left": "{0} осталось часов",
|
||||||
|
"beeswarm-less-than-hour-left": "осталось менее часа",
|
||||||
|
|
||||||
|
"handbook-beehive": "- <a href=\"handbook://block-orekiwoofsbeehives:beehiveroof-north\">Крыша улья</a><br>- <a href=\"handbook://block-orekiwoofsbeehives:beehivestand-north\">Подставка для улья</a>",
|
||||||
|
"handbook-beehive-title": "Украшения",
|
||||||
|
|
||||||
|
"config-desc-InformationVerbosity": "Какой объём информации должен отображаться при наведении курсора мыши на блок улья.",
|
||||||
|
"config-desc-DisableServerRecommended": "При значении true InformationVerbosityServer игнорируется, когда InformationVerbosityServerSideType = = ServerRecommended. Не действует, если выбран режим сервера ServerForced.",
|
||||||
|
"config-desc-BeehiveAmbientVolume": "Громкость окружающего звука улья. Обратите внимание, что для применения этого может потребоваться некоторое время, поскольку это работает после следующей «перезагрузки звука».",
|
||||||
|
"config-desc-InformationVerbosityServerSideType": "Определяет, является ли параметр InformationVerbosity только клиентским, рекомендуемым сервером или принудительно задаваемым сервером.",
|
||||||
|
"config-desc-InformationVerbosityServer": "Серверное значение подробности информации, используемое, когда InformationVerbosityServerSideType установлен в ServerRecommended или ServerForced.",
|
||||||
|
"config-desc-BeehiveRadius": "Радиус, в котором ульи влияют на посевы и в пределах которого пчёлы посещают цветы.",
|
||||||
|
"config-desc-YieldBoost": "Даёт ли близость улья прибавку к урожайности культур.",
|
||||||
|
"config-desc-SpeedBoost": "Даёт ли близость улья прибавку к скорости роста культур.",
|
||||||
|
"config-desc-GrowthSpeedBonus": "Бонус к скорости роста в радиусе улья (0.2 = до 20% быстрее). Работает только при включённом SpeedBoost.",
|
||||||
|
"config-desc-YieldMultiplier": "Множитель урожайности культур в радиусе улья (1.5 = 150%). Работает только при включённом YieldBoost.",
|
||||||
|
"config-desc-PlantsFullBoostDistanceToHive": "Сферический радиус, в котором культуры получают полный бонус роста. Далее бонус линейно уменьшается до расстояния BeehiveRadius.",
|
||||||
|
"config-desc-MinBeesForPlantBoost": "Минимальное количество пчёл в улье, необходимое для усиления роста ближайших растений.",
|
||||||
|
"config-desc-BoostCalculation": "Способ масштабирования бонуса роста культур в зависимости от численности пчёл после достижения MinBeesForPlantBoost.",
|
||||||
|
"config-desc-PopulationPercentForMaxBoost": "Процент от MaxBeePopulation, необходимый для получения максимального бонуса роста культур.",
|
||||||
|
"config-desc-MultipleBeehivesBoostCombination": "Способ объединения бонусов роста культур от нескольких ульев.",
|
||||||
|
"config-desc-MaxBeePopulation": "Максимальное количество пчёл в улье.",
|
||||||
|
"config-desc-InitialBeePopulation": "Численность пчёл, добавляемая в улей при помещении в него заселённого плетёного улья.",
|
||||||
|
"config-desc-BeehiveConsideredEmptyBelowPopulation": "Если численность пчёл ниже этого значения, улей считается достаточно пустым и может принять прилетающий рой.",
|
||||||
|
"config-desc-PopulationPercentRequirementForSwarm": "Процент от MaxBeePopulation, необходимый для того, чтобы улей мог выпустить рой.",
|
||||||
|
"config-desc-SwarmPopulationPercentage": "Процент пчёл, покидающих улей при образовании роя.",
|
||||||
|
"config-desc-SwarmCooldownDays": "Количество дней после выхода роя, в течение которых улей не может выпустить новый рой.",
|
||||||
|
"config-desc-SwarmReturnToOriginOnFailedMigrationPercent": "Процент пчёл роя, возвращающихся в исходный улей, если рой не смог заселиться в новый улей.",
|
||||||
|
"config-desc-BaseDeathsPerDay": "Количество пчёл, погибающих за день независимо от условий.",
|
||||||
|
"config-desc-FlowerThreshold": "Минимальное количество цветов, необходимое, чтобы избежать дополнительной гибели пчёл.",
|
||||||
|
"config-desc-DeathPerMissingFlower": "Дополнительная гибель пчёл в день за каждый недостающий цветок ниже порога.",
|
||||||
|
"config-desc-DoublingTimeDays": "Количество дней, за которое популяция пчёл удваивается. Используется только при расчёте положительного ежедневного прироста.",
|
||||||
|
"config-desc-BonusGrowthPerFilledFrame": "Дополнительное количество пчёл в день за каждую заполненную рамку.",
|
||||||
|
"config-desc-ReferenceFlowers": "Базовое количество цветов для расчёта скорости производства мёда (10 цветов + 25000 пчёл = 1 рамка в день).",
|
||||||
|
"config-desc-ReferenceBees": "Базовое количество пчёл для расчёта скорости производства мёда (10 цветов + 25000 пчёл = 1 рамка в день).",
|
||||||
|
"config-desc-MaxFlowersForHoneyProduction": "Максимальное количество цветов, учитываемых при расчёте производства мёда.",
|
||||||
|
"config-desc-WinterHardMode": "Включает усложнённый зимний режим с дополнительными зимними механиками.",
|
||||||
|
"config-desc-WinterDailyBeeDeathsWithoutFood": "Дополнительная ежедневная гибель пчёл зимой, когда нет заполненных/кормовых рамок. Работает только с включенным WinterHardMode.",
|
||||||
|
"config-desc-WinterFoodConsumptionMultiplier": "Коэффициент потребления зимних продуктов питания (0-1). При температуре от 10° C до 0° C этот эффект увеличивается в обратном направлении относительно производства мёда; при температуре ниже 0° C он остается максимальным. Работает только при включенном режиме WinterHardMode.",
|
||||||
|
"config-desc-MinTemperatureGrowth": "Температура, при которой прекращается рост пчёл и производство мёда.",
|
||||||
|
"config-desc-MaxTemperatureGrowth": "Температура, при которой рост пчёл и производство мёда достигают максимума.",
|
||||||
|
"config-desc-GreenhouseAffectsBeehive": "Дают ли теплицы повышение температуры в улье на 5°C.",
|
||||||
|
"config-desc-BeesPerParticle": "Количество пчёл, представленных каждой группой частиц. Если это 1000, а в улье 5000 пчёл, тогда улей породит не более 5 частиц.",
|
||||||
|
"config-desc-BeehiveAlwaysSpawnNumberOfBees": "Всегда создаёт указанное количество пчёл на блок улья независимо от их численности.",
|
||||||
|
"beehive-info-swarms-disabled": "Роение в этом улье отключено.",
|
||||||
|
"beehive-info-pre-swarm-building": "Пчёлы готовятся выпустить рой.",
|
||||||
|
"beehive-info-pre-swarm-progress": "Подготовка к выходу роя ({0})",
|
||||||
|
"beehive-info-pre-swarm-time-lessthanhourleft": "меньше часа",
|
||||||
|
"beehive-info-pre-swarm-time-hours": "осталось {0} ч",
|
||||||
|
"beehive-info-pre-swarm-time-1day": "остался 1 день",
|
||||||
|
"beehive-info-pre-swarm-time-days": "осталось {0} дн",
|
||||||
|
"beehive-info-pre-swarm-paused": "приостановлено из-за погодных условий",
|
||||||
|
"blockhelp-beehive-enable-swarm": "Включить роение",
|
||||||
|
"blockhelp-beehive-disable-swarm": "Отключить роение",
|
||||||
|
"config-desc-EnableSwarms": "Определяет, могут ли ульи выпускать рои.",
|
||||||
|
"config-desc-PreSwarmDurationHours": "Время в часах, за которое у пчёл накапливается готовность к роению (0–100%). Прогресс растёт только при MaxTemperatureGrowth или выше, иначе уменьшается.",
|
||||||
|
"config-desc-SwarmSettingAfterPlacing": "Будет ли в только что установленном улье включено или отключено роение. Можно изменить гаечным ключом.",
|
||||||
|
"setpopulation-desc": "Установить численность пчёл в улье, на который вы сейчас смотрите.",
|
||||||
|
"debugunload-desc": "Включить или отключить отладочные сообщения догоняющей обработки после выгрузки в чате сервера.",
|
||||||
|
"plantreg-blockpertick-desc": "Показать или задать параметр BlocksPerTick (0–1000) для сканирования реестра растений.",
|
||||||
|
"plantreg-blockpertick-parse-error": "Не удалось распознать значение. Используйте целое число от 0 до 1000.",
|
||||||
|
"plantreg-unavailable": "Система реестра растений недоступна."
|
||||||
|
}
|
||||||
52
OrekiWoofsBeehives/assets/orekiwoofsbeehives/lang/uk.json
Normal file
52
OrekiWoofsBeehives/assets/orekiwoofsbeehives/lang/uk.json
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
{
|
||||||
|
"block-beehive-*": "Вулик",
|
||||||
|
"block-beehivestand-*": "Підставка для вулика",
|
||||||
|
"block-beehiveroof-*": "Дах вулика",
|
||||||
|
"block-beehiveframe-empty": "Порожня рамка вулика",
|
||||||
|
"block-beehiveframe-filled": "Наповнена рамка вулика",
|
||||||
|
"block-beehiveframe-filled-feed": "Рамка вулика з поживкою",
|
||||||
|
"beehiveframe-feed-percent": ">>>percent<<<%",
|
||||||
|
"beehive-info-no-bees": "Бджіл немає. Використовуйте заселену сапетку, щоб заселити вулик.",
|
||||||
|
"beehive-info-frames-boosting": "Запаси меду сприяють зростанню чисельності населення.",
|
||||||
|
"beehive-info-frames-sacrificing": "Бджоли жертвують половиною свого приросту популяції, щоб виробляти мед.",
|
||||||
|
"beehive-info-population": "Популяція бджіл: >>>beePopulation<<< / >>>maxBeePopulation<<< (>>>beeProductionMultiplier<<<x виробництво меду)",
|
||||||
|
"beehive-info-population-v2": "Популяція бджіл: >>>beePopulation<<< / >>>maxBeePopulation<<< (>>>beeProductionMultiplier<<<x виробництво меду)",
|
||||||
|
"beehive-info-population-none": "Популяція бджіл: Немає",
|
||||||
|
"beehive-info-population-low": "Популяція бджіл: Низька",
|
||||||
|
"beehive-info-population-medium": "Популяція бджіл: Середня",
|
||||||
|
"beehive-info-population-high": "Популяція бджіл: Висока",
|
||||||
|
"beehive-info-population-veryhigh": "Популяція бджіл: Дуже висока",
|
||||||
|
"beehive-info-flowers": "Квіти навколо: >>>flowersAround<<<, Врожай: >>>cropsAround<<<, Effectively: >>>effectiveFlowers<<< (>>>flowerProductionMultiplier<<<x виробництво меду)",
|
||||||
|
"beehive-info-flowers-simple": "Квіти навколо: >>>flowersAround<<<, Врожай: >>>cropsAround<<<",
|
||||||
|
"beehive-info-flowers-scanning-suffix": "(все ще розвідує територію)",
|
||||||
|
"beehive-info-frames-mixed": "Рамки: >>>filledFrames<<< наповнена (<font color=\"#00aa00\">+>>>filledFrameBonus<<<</font> бджіл/день), >>>emptyFrames<<< порожня (<font color=\"#994d00\">-0.5x</font> бджіл/день)",
|
||||||
|
"beehive-info-frames-filled": "Рамки: >>>filledFrames<<< наповнена (<font color=\"#00aa00\">+>>>filledFrameBonus<<<</font> бджіл/день)",
|
||||||
|
"beehive-info-frames-empty": "Рамки: >>>emptyFrames<<< порожня (<font color=\"#ffff00\">-0.5x</font> бджіл/день)",
|
||||||
|
"beehive-info-frames-total": "Рамки: >>>totalFrames<<<",
|
||||||
|
"beehive-info-progress-filling": "Поступ рамки: >>>honeyProgress<<<% (fills in >>>daysToFill<<< днів)",
|
||||||
|
"beehive-info-progress-noproduction": "Поступ рамки: >>>honeyProgress<<<% (немає виробництва - потрібні бджоли і квіти)",
|
||||||
|
"beehive-info-progress-v1-noproduction": "Виробництво меду відсутнє",
|
||||||
|
"beehive-info-progress-v1-lessthanday": "Рамка заповниться менш ніж за день",
|
||||||
|
"beehive-info-progress-v1-days": "Заповнить рамку за >>>days<<< днів",
|
||||||
|
"beehive-info-all-filled": "Всі рамки заповнені! Додайте порожні рамки, щоб продовжити виробництво.",
|
||||||
|
"beehive-info-no-frames": "У вулику немає рамок. Додайте порожні рамки, щоб розпочати виробництво.",
|
||||||
|
"beehive-info-temperature": "Температура: >>>temperature<<<°C (>>>multiplier<<<x ефективність та зміна чисельності населення)",
|
||||||
|
"beehive-info-temperature-cold": "Холодна температура змушує бджіл частіше залишатися всередині.",
|
||||||
|
"beehive-info-temperature-overwintering": "Бджоли зимують.",
|
||||||
|
"beehive-info-population-change": "Зміна популяції: >>>changeSign<<<>>>netChange<<</day (growth: +>>>dailyGrowth<<<, decrease: ->>>dailyDeaths<<<)",
|
||||||
|
"beehive-info-population-status-decreasing": "Зменшення популяції",
|
||||||
|
"beehive-info-population-status-stagnant": "Стагнація популяції",
|
||||||
|
"beehive-info-population-status-slowgrowth": "Повільне зростання популяції",
|
||||||
|
"beehive-info-population-status-growing": "Зростання популяції",
|
||||||
|
"beehive-info-honey-production": "Виробництво меду: >>>framesPerDay<<<x (>>>daysPerFrame<<< днів на рамку)",
|
||||||
|
"beehive-info-honey-production-simple": "Виробництво меду: >>>framesPerDay<<<x",
|
||||||
|
"beehive-info-feed-current": "Поточна рамка з кормом: >>>feedRemainingPercent<<<% (>>>daysPerFrame<<< днів на рамку)",
|
||||||
|
"blockinfo-in-range-beehive": "У діапазоні {p0:# вуликів|# вулик|# вуликів}",
|
||||||
|
"blockinfo-growth-speed-bonus": "{0}% швидкість росту",
|
||||||
|
"blockinfo-yield-bonus": "{0}% урожаю",
|
||||||
|
"blockinfo-will-be-pollinated": "Буде запилюватися {p0:# на пізніших стадіях росту|# на пізнішій стадії росту|# на пізніших стадіях росту}",
|
||||||
|
"blockinfo-pollinated": "Запилений",
|
||||||
|
"beehiveScoutingProgress": "Прогрес розвідки",
|
||||||
|
"handbook-beehive": "- <a href=\"handbook://block-orekiwoofsbeehives:beehiveroof-north\">Дах</a><br>- <a href=\"handbook://block-orekiwoofsbeehives:beehivestand-north\">підставка</a>",
|
||||||
|
"handbook-beehive-title": "Прикраси"
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"op": "addMerge",
|
||||||
|
"path": "/behaviors",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"name": "BlockBehaviorBeehiveAffected"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"file": "game:blocktypes/soil/farmland",
|
||||||
|
"side": "Server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "addMerge",
|
||||||
|
"path": "/entityBehaviors",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"name": "BlockEntityBehaviorBeehiveYieldMultiplier"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"file": "game:blocktypes/soil/farmland",
|
||||||
|
"side": "Server"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"op": "addMerge",
|
||||||
|
"path": "/behaviors",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"name": "BlockBehaviorBeehiveAffected"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"file": "game:blocktypes/plant/**",
|
||||||
|
"side": "Server"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"op": "addMerge",
|
||||||
|
"path": "/behaviors",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"name": "BlockBehaviorBeehiveAffected"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"file": "game:blocktypes/clay/flowerpot-fancy",
|
||||||
|
"side": "Server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "addMerge",
|
||||||
|
"path": "/behaviors",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"name": "BlockBehaviorBeehiveAffected"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"file": "game:blocktypes/clay/fired/flowerpot",
|
||||||
|
"side": "Server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "addMerge",
|
||||||
|
"path": "/behaviors",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"name": "BlockBehaviorBeehiveAffected"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"file": "game:blocktypes/clay/planter-fancy",
|
||||||
|
"side": "Server"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"op": "addMerge",
|
||||||
|
"path": "/behaviors",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"name": "BlockBehaviorBeehiveAffected"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"file": "game:blocktypes/clay/fired/planter",
|
||||||
|
"side": "Server"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"op": "addMerge",
|
||||||
|
"path": "/entityBehaviors",
|
||||||
|
"value": [
|
||||||
|
{
|
||||||
|
"name": "BlockEntityBehaviorVanillaSkepSwarmTarget"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"file": "game:blocktypes/reed/skep",
|
||||||
|
"side": "Server"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"ingredientPattern": "PNP,PSP,P P",
|
||||||
|
"ingredients": {
|
||||||
|
"N": { "type": "item", "code": "game:metalnailsandstrips-*", "quantity": 2 },
|
||||||
|
"P": { "type": "item", "code": "game:plank-*", "quantity": 2 },
|
||||||
|
"S": { "type": "item", "code": "game:beeswax" }
|
||||||
|
},
|
||||||
|
"width": 3,
|
||||||
|
"height": 3,
|
||||||
|
"output": { "type": "block", "code": "orekiwoofsbeehives:beehive-north" }
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"ingredientPattern": "PPP,PLP,PPP",
|
||||||
|
"ingredients": {
|
||||||
|
"P": { "type": "item", "code": "game:plank-*" },
|
||||||
|
"L": { "type": "block", "code": "game:linen-*" }
|
||||||
|
},
|
||||||
|
"width": 3,
|
||||||
|
"height": 3,
|
||||||
|
"output": { "type": "block", "code": "orekiwoofsbeehives:beehiveframe-empty", "quantity": 1 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ingredientPattern": "PL",
|
||||||
|
"ingredients": {
|
||||||
|
"P": { "type": "block", "code": "orekiwoofsbeehives:beehiveframe-empty", "quantity": 1 },
|
||||||
|
"L": { "type": "item", "code": "game:honeycomb", "quantity": 1 }
|
||||||
|
},
|
||||||
|
"width": 2,
|
||||||
|
"height": 1,
|
||||||
|
"shapeless": true,
|
||||||
|
"output": { "type": "block", "code": "orekiwoofsbeehives:beehiveframe-filled", "quantity": 1 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ingredientPattern": "PL",
|
||||||
|
"ingredients": {
|
||||||
|
"P": { "type": "block", "code": "orekiwoofsbeehives:beehiveframe-empty", "quantity": 1 },
|
||||||
|
"L": { "type": "item", "code": "game:beeswax", "quantity": 2 }
|
||||||
|
},
|
||||||
|
"width": 2,
|
||||||
|
"height": 1,
|
||||||
|
"shapeless": true,
|
||||||
|
"output": { "type": "block", "code": "orekiwoofsbeehives:beehiveframe-filled-feed", "quantity": 1 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ingredientPattern": "PL",
|
||||||
|
"ingredients": {
|
||||||
|
"P": { "type": "block", "code": "orekiwoofsbeehives:beehiveframe-empty", "quantity": 1 },
|
||||||
|
"L": { "type": "block", "code": "game:bowl-*-fired" }
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"liquidContainerProps": {
|
||||||
|
"requiresContent": { "type": "item", "code": "honeyportion" },
|
||||||
|
"requiresLitres": 0.4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 2,
|
||||||
|
"height": 1,
|
||||||
|
"shapeless": true,
|
||||||
|
"output": { "type": "block", "code": "orekiwoofsbeehives:beehiveframe-filled-feed", "quantity": 1 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ingredientPattern": "PL",
|
||||||
|
"ingredients": {
|
||||||
|
"P": { "type": "block", "code": "orekiwoofsbeehives:beehiveframe-empty", "quantity": 1 },
|
||||||
|
"L": { "type": "block", "code": "game:woodbucket" }
|
||||||
|
},
|
||||||
|
"attributes": {
|
||||||
|
"liquidContainerProps": {
|
||||||
|
"requiresContent": { "type": "item", "code": "honeyportion" },
|
||||||
|
"requiresLitres": 0.4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 2,
|
||||||
|
"height": 1,
|
||||||
|
"shapeless": true,
|
||||||
|
"output": { "type": "block", "code": "orekiwoofsbeehives:beehiveframe-filled-feed", "quantity": 1 }
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"ingredientPattern": "_B_,___,P_P",
|
||||||
|
"ingredients": {
|
||||||
|
"B": { "type": "item", "code": "game:plank-*", "quantity": 2 },
|
||||||
|
"P": { "type": "item", "code": "game:plank-*" }
|
||||||
|
},
|
||||||
|
"width": 3,
|
||||||
|
"height": 3,
|
||||||
|
"output": { "type": "block", "code": "orekiwoofsbeehives:beehiveroof-north", "quantity": 1 }
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"ingredientPattern": "B_B,P_P",
|
||||||
|
"ingredients": {
|
||||||
|
"B": { "type": "item", "code": "game:plank-*", "quantity": 2 },
|
||||||
|
"P": { "type": "item", "code": "game:plank-*" }
|
||||||
|
},
|
||||||
|
"width": 3,
|
||||||
|
"height": 2,
|
||||||
|
"output": { "type": "block", "code": "orekiwoofsbeehives:beehivestand-north", "quantity": 1 }
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"ingredientPattern": "K_,F_",
|
||||||
|
"ingredients": {
|
||||||
|
"K": {
|
||||||
|
"type": "item",
|
||||||
|
"code": "game:knife-*",
|
||||||
|
"isTool": true
|
||||||
|
},
|
||||||
|
"F": {
|
||||||
|
"type": "block",
|
||||||
|
"code": "orekiwoofsbeehives:beehiveframe-filled",
|
||||||
|
"returnedStack": {
|
||||||
|
"type": "block",
|
||||||
|
"code": "orekiwoofsbeehives:beehiveframe-empty"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"width": 2,
|
||||||
|
"height": 2,
|
||||||
|
"output": {
|
||||||
|
"type": "item",
|
||||||
|
"code": "game:honeycomb",
|
||||||
|
"quantity": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
{
|
||||||
|
"editor": {
|
||||||
|
"allAngles": true
|
||||||
|
},
|
||||||
|
"textureWidth": 16,
|
||||||
|
"textureHeight": 16,
|
||||||
|
"textures": {
|
||||||
|
"wood": "game:block/wood/henbox/sides"
|
||||||
|
},
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"name": "BottomLeft",
|
||||||
|
"from": [ 0, 0, 0 ],
|
||||||
|
"to": [ 7, 6, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 9, 10, 16, 16 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 0, 10, 16, 16 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 0, 10, 7, 16 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0, 10, 16, 16 ] },
|
||||||
|
"up": { "texture": "#wood", "uv": [ 0, 0, 7, 16 ] },
|
||||||
|
"down": { "texture": "#wood", "uv": [ 0, 0, 7, 16 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomRight",
|
||||||
|
"from": [ 9, 0, 0 ],
|
||||||
|
"to": [ 16, 6, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 0, 10, 7, 16 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 0, 10, 16, 16 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 9, 10, 16, 16 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0, 10, 16, 16 ] },
|
||||||
|
"up": { "texture": "#wood", "uv": [ 9, 0, 16, 16 ] },
|
||||||
|
"down": { "texture": "#wood", "uv": [ 9, 0, 16, 16 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomCenterLower",
|
||||||
|
"from": [ 7, 0, 1 ],
|
||||||
|
"to": [ 9, 2.5, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#wood", "uv": [ 1, 13.5, 16, 16 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 7, 13.5, 9, 16 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 1, 13.5, 16, 16 ] },
|
||||||
|
"up": { "texture": "#wood", "uv": [ 7, 1, 9, 16 ] },
|
||||||
|
"down": { "texture": "#wood", "uv": [ 7, 1, 9, 16 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomCenterUpper",
|
||||||
|
"from": [ 7, 4.5, 1 ],
|
||||||
|
"to": [ 9, 6, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#wood", "uv": [ 1, 10, 16, 11.5 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 7, 10, 9, 11.5 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 1, 10, 16, 11.5 ] },
|
||||||
|
"up": { "texture": "#wood", "uv": [ 7, 1, 9, 16 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomFrontLower",
|
||||||
|
"from": [ 7, 0, 0 ],
|
||||||
|
"to": [ 9, 2.5, 1.5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 7, 13.5, 9, 16 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 0, 13.5, 1.5, 16 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0, 13.5, 1.5, 16 ] },
|
||||||
|
"up": { "texture": "#wood", "uv": [ 7, 0, 9, 1.5 ] },
|
||||||
|
"down": { "texture": "#wood", "uv": [ 7, 0, 9, 1.5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomFrontUpper",
|
||||||
|
"from": [ 7, 4.5, 0 ],
|
||||||
|
"to": [ 9, 6, 1.5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 7, 10, 9, 11.5 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 0, 10, 1.5, 11.5 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0, 10, 1.5, 11.5 ] },
|
||||||
|
"up": { "texture": "#wood", "uv": [ 7, 0, 9, 1.5 ] },
|
||||||
|
"down": { "texture": "#wood", "uv": [ 7, 0, 9, 1.5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomIndentBack",
|
||||||
|
"from": [ 7, 2.5, 1.5 ],
|
||||||
|
"to": [ 9, 4.5, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 7, 11.5, 9, 13.5 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 1.5, 11.5, 16, 13.5 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 7, 11.5, 9, 13.5 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 1.5, 11.5, 16, 13.5 ] },
|
||||||
|
"up": { "texture": "#wood", "uv": [ 7, 1.5, 9, 16 ] },
|
||||||
|
"down": { "texture": "#wood", "uv": [ 7, 1.5, 9, 16 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Top",
|
||||||
|
"from": [ 0, 14, 0 ],
|
||||||
|
"to": [ 16, 16, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 0, 0, 16, 2 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 0, 0, 16, 2 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 0, 0, 16, 2 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0, 0, 16, 2 ] },
|
||||||
|
"up": { "texture": "#wood", "uv": [ 0, 0, 16, 16 ] },
|
||||||
|
"down": { "texture": "#wood", "uv": [ 0, 0, 16, 16 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Back",
|
||||||
|
"from": [ 0, 6, 14 ],
|
||||||
|
"to": [ 16, 14, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 0, 2, 16, 10 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 14, 2, 16, 10 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 0, 2, 16, 10 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0, 2, 2, 10 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Right",
|
||||||
|
"from": [ 0, 6, 0 ],
|
||||||
|
"to": [ 2, 14, 14 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 0, 2, 2, 10 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 0, 2, 14, 10 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 14, 2, 16, 10 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0, 2, 14, 10 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Left",
|
||||||
|
"from": [ 14, 6, 0 ],
|
||||||
|
"to": [ 16, 14, 14 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 14, 2, 16, 10 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 2, 2, 16, 10 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 0, 2, 2, 10 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0, 2, 14, 10 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Front",
|
||||||
|
"from": [ 2, 6, 1 ],
|
||||||
|
"to": [ 14, 14, 2 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 2, 2, 14, 10 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 0.5, 2, 2, 10 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 2, 2, 14, 10 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0.5, 2, 2, 10 ] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
{
|
||||||
|
"editor": {
|
||||||
|
"allAngles": true
|
||||||
|
},
|
||||||
|
"textureWidth": 16,
|
||||||
|
"textureHeight": 16,
|
||||||
|
"textures": {
|
||||||
|
"wood": "game:block/wood/henbox/sides"
|
||||||
|
},
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"name": "BottomLeft",
|
||||||
|
"from": [ 0, 0, 0 ],
|
||||||
|
"to": [ 7, 6, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 9, 10, 16, 16 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 0, 10, 16, 16 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 0, 10, 7, 16 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0, 10, 16, 16 ] },
|
||||||
|
"up": { "texture": "#wood", "uv": [ 0, 0, 7, 16 ] },
|
||||||
|
"down": { "texture": "#wood", "uv": [ 0, 0, 7, 16 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomRight",
|
||||||
|
"from": [ 9, 0, 0 ],
|
||||||
|
"to": [ 16, 6, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 0, 10, 7, 16 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 0, 10, 16, 16 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 9, 10, 16, 16 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0, 10, 16, 16 ] },
|
||||||
|
"up": { "texture": "#wood", "uv": [ 9, 0, 16, 16 ] },
|
||||||
|
"down": { "texture": "#wood", "uv": [ 9, 0, 16, 16 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomCenterLower",
|
||||||
|
"from": [ 7, 0, 1 ],
|
||||||
|
"to": [ 9, 2.5, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#wood", "uv": [ 1, 13.5, 16, 16 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 7, 13.5, 9, 16 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 1, 13.5, 16, 16 ] },
|
||||||
|
"up": { "texture": "#wood", "uv": [ 7, 1, 9, 16 ] },
|
||||||
|
"down": { "texture": "#wood", "uv": [ 7, 1, 9, 16 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomCenterUpper",
|
||||||
|
"from": [ 7, 4.5, 1 ],
|
||||||
|
"to": [ 9, 6, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#wood", "uv": [ 1, 10, 16, 11.5 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 7, 10, 9, 11.5 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 1, 10, 16, 11.5 ] },
|
||||||
|
"up": { "texture": "#wood", "uv": [ 7, 1, 9, 16 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomFrontLower",
|
||||||
|
"from": [ 7, 0, 0 ],
|
||||||
|
"to": [ 9, 2.5, 1.5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 7, 13.5, 9, 16 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 0, 13.5, 1.5, 16 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0, 13.5, 1.5, 16 ] },
|
||||||
|
"up": { "texture": "#wood", "uv": [ 7, 0, 9, 1.5 ] },
|
||||||
|
"down": { "texture": "#wood", "uv": [ 7, 0, 9, 1.5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomFrontUpper",
|
||||||
|
"from": [ 7, 4.5, 0 ],
|
||||||
|
"to": [ 9, 6, 1.5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 7, 10, 9, 11.5 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 0, 10, 1.5, 11.5 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0, 10, 1.5, 11.5 ] },
|
||||||
|
"up": { "texture": "#wood", "uv": [ 7, 0, 9, 1.5 ] },
|
||||||
|
"down": { "texture": "#wood", "uv": [ 7, 0, 9, 1.5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomIndentBack",
|
||||||
|
"from": [ 7, 2.5, 1.5 ],
|
||||||
|
"to": [ 9, 4.5, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 7, 11.5, 9, 13.5 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 1.5, 11.5, 16, 13.5 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 7, 11.5, 9, 13.5 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 1.5, 11.5, 16, 13.5 ] },
|
||||||
|
"up": { "texture": "#wood", "uv": [ 7, 1.5, 9, 16 ] },
|
||||||
|
"down": { "texture": "#wood", "uv": [ 7, 1.5, 9, 16 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Top",
|
||||||
|
"from": [ 0, 14, 0 ],
|
||||||
|
"to": [ 16, 16, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 0, 0, 16, 2 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 0, 0, 16, 2 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 0, 0, 16, 2 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0, 0, 16, 2 ] },
|
||||||
|
"up": { "texture": "#wood", "uv": [ 0, 0, 16, 16 ] },
|
||||||
|
"down": { "texture": "#wood", "uv": [ 0, 0, 16, 16 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Back",
|
||||||
|
"from": [ 0, 6, 14 ],
|
||||||
|
"to": [ 16, 14, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 0, 2, 16, 10 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 14, 2, 16, 10 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 0, 2, 16, 10 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0, 2, 2, 10 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Right",
|
||||||
|
"from": [ 0, 6, 0 ],
|
||||||
|
"to": [ 2, 14, 14 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 0, 2, 2, 10 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 0, 2, 14, 10 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 14, 2, 16, 10 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0, 2, 14, 10 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Left",
|
||||||
|
"from": [ 14, 6, 0 ],
|
||||||
|
"to": [ 16, 14, 14 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#wood", "uv": [ 14, 2, 16, 10 ] },
|
||||||
|
"east": { "texture": "#wood", "uv": [ 2, 2, 16, 10 ] },
|
||||||
|
"south": { "texture": "#wood", "uv": [ 0, 2, 2, 10 ] },
|
||||||
|
"west": { "texture": "#wood", "uv": [ 0, 2, 14, 10 ] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,926 @@
|
|||||||
|
{
|
||||||
|
"editor": {
|
||||||
|
"allAngles": false,
|
||||||
|
"entityTextureMode": false
|
||||||
|
},
|
||||||
|
"textureWidth": 16,
|
||||||
|
"textureHeight": 16,
|
||||||
|
"textures": {
|
||||||
|
"frame": "game:block/wood-generic",
|
||||||
|
"center": "game:block/linen"
|
||||||
|
},
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"name": "Frame-0",
|
||||||
|
"from": [ 3, 0.45, 5 ],
|
||||||
|
"to": [ 13, 0.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder-0",
|
||||||
|
"from": [ 2, 0, 11 ],
|
||||||
|
"to": [ 14, 1, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder-0",
|
||||||
|
"from": [ 2, 0, 4 ],
|
||||||
|
"to": [ 14, 1, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder-0",
|
||||||
|
"from": [ 2, 0, 5 ],
|
||||||
|
"to": [ 3, 1, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder-0",
|
||||||
|
"from": [ 13, 0, 5 ],
|
||||||
|
"to": [ 14, 1, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frame-1",
|
||||||
|
"from": [ 3, 1.45, 5 ],
|
||||||
|
"to": [ 13, 1.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder-1",
|
||||||
|
"from": [ 2, 1, 11 ],
|
||||||
|
"to": [ 14, 2, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder-1",
|
||||||
|
"from": [ 2, 1, 4 ],
|
||||||
|
"to": [ 14, 2, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder-1",
|
||||||
|
"from": [ 2, 1, 5 ],
|
||||||
|
"to": [ 3, 2, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder-1",
|
||||||
|
"from": [ 13, 1, 5 ],
|
||||||
|
"to": [ 14, 2, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frame-2",
|
||||||
|
"from": [ 3, 2.45, 5 ],
|
||||||
|
"to": [ 13, 2.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder-2",
|
||||||
|
"from": [ 2, 2, 11 ],
|
||||||
|
"to": [ 14, 3, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder-2",
|
||||||
|
"from": [ 2, 2, 4 ],
|
||||||
|
"to": [ 14, 3, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder-2",
|
||||||
|
"from": [ 2, 2, 5 ],
|
||||||
|
"to": [ 3, 3, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder-2",
|
||||||
|
"from": [ 13, 2, 5 ],
|
||||||
|
"to": [ 14, 3, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frame-3",
|
||||||
|
"from": [ 3, 3.45, 5 ],
|
||||||
|
"to": [ 13, 3.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder-3",
|
||||||
|
"from": [ 2, 3, 11 ],
|
||||||
|
"to": [ 14, 4, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder-3",
|
||||||
|
"from": [ 2, 3, 4 ],
|
||||||
|
"to": [ 14, 4, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder-3",
|
||||||
|
"from": [ 2, 3, 5 ],
|
||||||
|
"to": [ 3, 4, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder-3",
|
||||||
|
"from": [ 13, 3, 5 ],
|
||||||
|
"to": [ 14, 4, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frame-4",
|
||||||
|
"from": [ 3, 4.45, 5 ],
|
||||||
|
"to": [ 13, 4.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder-4",
|
||||||
|
"from": [ 2, 4, 11 ],
|
||||||
|
"to": [ 14, 5, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder-4",
|
||||||
|
"from": [ 2, 4, 4 ],
|
||||||
|
"to": [ 14, 5, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder-4",
|
||||||
|
"from": [ 2, 4, 5 ],
|
||||||
|
"to": [ 3, 5, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder-4",
|
||||||
|
"from": [ 13, 4, 5 ],
|
||||||
|
"to": [ 14, 5, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frame-5",
|
||||||
|
"from": [ 3, 5.45, 5 ],
|
||||||
|
"to": [ 13, 5.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder-5",
|
||||||
|
"from": [ 2, 5, 11 ],
|
||||||
|
"to": [ 14, 6, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder-5",
|
||||||
|
"from": [ 2, 5, 4 ],
|
||||||
|
"to": [ 14, 6, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder-5",
|
||||||
|
"from": [ 2, 5, 5 ],
|
||||||
|
"to": [ 3, 6, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder-5",
|
||||||
|
"from": [ 13, 5, 5 ],
|
||||||
|
"to": [ 14, 6, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frame-6",
|
||||||
|
"from": [ 3, 6.45, 5 ],
|
||||||
|
"to": [ 13, 6.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder-6",
|
||||||
|
"from": [ 2, 6, 11 ],
|
||||||
|
"to": [ 14, 7, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder-6",
|
||||||
|
"from": [ 2, 6, 4 ],
|
||||||
|
"to": [ 14, 7, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder-6",
|
||||||
|
"from": [ 2, 6, 5 ],
|
||||||
|
"to": [ 3, 7, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder-6",
|
||||||
|
"from": [ 13, 6, 5 ],
|
||||||
|
"to": [ 14, 7, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frame-7",
|
||||||
|
"from": [ 3, 7.45, 5 ],
|
||||||
|
"to": [ 13, 7.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder-7",
|
||||||
|
"from": [ 2, 7, 11 ],
|
||||||
|
"to": [ 14, 8, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder-7",
|
||||||
|
"from": [ 2, 7, 4 ],
|
||||||
|
"to": [ 14, 8, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder-7",
|
||||||
|
"from": [ 2, 7, 5 ],
|
||||||
|
"to": [ 3, 8, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder-7",
|
||||||
|
"from": [ 13, 7, 5 ],
|
||||||
|
"to": [ 14, 8, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frame-8",
|
||||||
|
"from": [ 3, 8.45, 5 ],
|
||||||
|
"to": [ 13, 8.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder-8",
|
||||||
|
"from": [ 2, 8, 11 ],
|
||||||
|
"to": [ 14, 9, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder-8",
|
||||||
|
"from": [ 2, 8, 4 ],
|
||||||
|
"to": [ 14, 9, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder-8",
|
||||||
|
"from": [ 2, 8, 5 ],
|
||||||
|
"to": [ 3, 9, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder-8",
|
||||||
|
"from": [ 13, 8, 5 ],
|
||||||
|
"to": [ 14, 9, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frame-9",
|
||||||
|
"from": [ 3, 9.45, 5 ],
|
||||||
|
"to": [ 13, 9.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder-9",
|
||||||
|
"from": [ 2, 9, 11 ],
|
||||||
|
"to": [ 14, 10, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder-9",
|
||||||
|
"from": [ 2, 9, 4 ],
|
||||||
|
"to": [ 14, 10, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder-9",
|
||||||
|
"from": [ 2, 9, 5 ],
|
||||||
|
"to": [ 3, 10, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder-9",
|
||||||
|
"from": [ 13, 9, 5 ],
|
||||||
|
"to": [ 14, 10, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frame-10",
|
||||||
|
"from": [ 3, 10.45, 5 ],
|
||||||
|
"to": [ 13, 10.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder-10",
|
||||||
|
"from": [ 2, 10, 11 ],
|
||||||
|
"to": [ 14, 11, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder-10",
|
||||||
|
"from": [ 2, 10, 4 ],
|
||||||
|
"to": [ 14, 11, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder-10",
|
||||||
|
"from": [ 2, 10, 5 ],
|
||||||
|
"to": [ 3, 11, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder-10",
|
||||||
|
"from": [ 13, 10, 5 ],
|
||||||
|
"to": [ 14, 11, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frame-11",
|
||||||
|
"from": [ 3, 11.45, 5 ],
|
||||||
|
"to": [ 13, 11.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder-11",
|
||||||
|
"from": [ 2, 11, 11 ],
|
||||||
|
"to": [ 14, 12, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder-11",
|
||||||
|
"from": [ 2, 11, 4 ],
|
||||||
|
"to": [ 14, 12, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder-11",
|
||||||
|
"from": [ 2, 11, 5 ],
|
||||||
|
"to": [ 3, 12, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder-11",
|
||||||
|
"from": [ 13, 11, 5 ],
|
||||||
|
"to": [ 14, 12, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frame-12",
|
||||||
|
"from": [ 3, 12.45, 5 ],
|
||||||
|
"to": [ 13, 12.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder-12",
|
||||||
|
"from": [ 2, 12, 11 ],
|
||||||
|
"to": [ 14, 13, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder-12",
|
||||||
|
"from": [ 2, 12, 4 ],
|
||||||
|
"to": [ 14, 13, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder-12",
|
||||||
|
"from": [ 2, 12, 5 ],
|
||||||
|
"to": [ 3, 13, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder-12",
|
||||||
|
"from": [ 13, 12, 5 ],
|
||||||
|
"to": [ 14, 13, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frame-13",
|
||||||
|
"from": [ 3, 13.45, 5 ],
|
||||||
|
"to": [ 13, 13.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder-13",
|
||||||
|
"from": [ 2, 13, 11 ],
|
||||||
|
"to": [ 14, 14, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder-13",
|
||||||
|
"from": [ 2, 13, 4 ],
|
||||||
|
"to": [ 14, 14, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder-13",
|
||||||
|
"from": [ 2, 13, 5 ],
|
||||||
|
"to": [ 3, 14, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder-13",
|
||||||
|
"from": [ 13, 13, 5 ],
|
||||||
|
"to": [ 14, 14, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frame-14",
|
||||||
|
"from": [ 3, 14.45, 5 ],
|
||||||
|
"to": [ 13, 14.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder-14",
|
||||||
|
"from": [ 2, 14, 11 ],
|
||||||
|
"to": [ 14, 15, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder-14",
|
||||||
|
"from": [ 2, 14, 4 ],
|
||||||
|
"to": [ 14, 15, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder-14",
|
||||||
|
"from": [ 2, 14, 5 ],
|
||||||
|
"to": [ 3, 15, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder-14",
|
||||||
|
"from": [ 13, 14, 5 ],
|
||||||
|
"to": [ 14, 15, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Frame-15",
|
||||||
|
"from": [ 3, 15.45, 5 ],
|
||||||
|
"to": [ 13, 15.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder-15",
|
||||||
|
"from": [ 2, 15, 11 ],
|
||||||
|
"to": [ 14, 16, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder-15",
|
||||||
|
"from": [ 2, 15, 4 ],
|
||||||
|
"to": [ 14, 16, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder-15",
|
||||||
|
"from": [ 2, 15, 5 ],
|
||||||
|
"to": [ 3, 16, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder-15",
|
||||||
|
"from": [ 13, 15, 5 ],
|
||||||
|
"to": [ 14, 16, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
{
|
||||||
|
"editor": {
|
||||||
|
"allAngles": false
|
||||||
|
},
|
||||||
|
"textureWidth": 16,
|
||||||
|
"textureHeight": 16,
|
||||||
|
"textures": {
|
||||||
|
"roof-plank": "game:block/wood/planks/oak1"
|
||||||
|
},
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"name": "origin",
|
||||||
|
"from": [ 0.0, 0.0, 0.0 ],
|
||||||
|
"to": [ 0.0, 0.0, 0.0 ],
|
||||||
|
"rotationOrigin": [ 8.0, 0.0, 8.0 ],
|
||||||
|
"faces": {},
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"name": "base left",
|
||||||
|
"from": [ 0.0, 0.0, 0.0 ],
|
||||||
|
"to": [ 4.5, 2.0, 16.0 ],
|
||||||
|
"rotationOrigin": [ 0.0, 0.0, 0.1 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#roof-plank", "uv": [ 0.0, 14.0, 4.5, 16.0 ] },
|
||||||
|
"south": { "texture": "#roof-plank", "uv": [ 0.0, 14.0, 4.5, 16.0 ] },
|
||||||
|
"down": { "texture": "#roof-plank", "uv": [ 0.0, 0.0, 4.5, 16.0 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "base center",
|
||||||
|
"from": [ 4.5, 0.0, 0.0 ],
|
||||||
|
"to": [ 11.5, 3.0, 16.0 ],
|
||||||
|
"rotationOrigin": [ 0.0, 0.0, 0.1 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#roof-plank", "uv": [ 4.5, 13.0, 11.5, 16.0 ] },
|
||||||
|
"south": { "texture": "#roof-plank", "uv": [ 4.5, 13.0, 11.5, 16.0 ] },
|
||||||
|
"down": { "texture": "#roof-plank", "uv": [ 4.5, 0.0, 11.5, 16.0 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "base right",
|
||||||
|
"from": [ 11.5, 0.0, 0.0 ],
|
||||||
|
"to": [ 16.0, 2.0, 16.0 ],
|
||||||
|
"rotationOrigin": [ 0.0, 0.0, 0.1 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#roof-plank", "uv": [ 11.5, 14.0, 16.0, 16.0 ] },
|
||||||
|
"south": { "texture": "#roof-plank", "uv": [ 11.5, 14.0, 16.0, 16.0 ] },
|
||||||
|
"down": { "texture": "#roof-plank", "uv": [ 11.5, 0.0, 16.0, 16.0 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "slanted left",
|
||||||
|
"from": [ -5.0, 3.0, -1.001 ],
|
||||||
|
"to": [ 8.5, 5.25, 17.009 ],
|
||||||
|
"rotationOrigin": [ 8.0, 4.0, 8.0 ],
|
||||||
|
"rotationZ": 22.5,
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#roof-plank", "uv": [ 0.0, 0.0, 13.5, 2.25 ] },
|
||||||
|
"south": { "texture": "#roof-plank", "uv": [ 0.0, 0.0, 13.5, 2.25 ] },
|
||||||
|
"west": { "texture": "#roof-plank", "uv": [ 0.0, 0.0, 18.0, 2.25 ] },
|
||||||
|
"up": { "texture": "#roof-plank", "uv": [ 0.0, 0.0, 18.0, 13.5 ], "rotation": 90 },
|
||||||
|
"down": { "texture": "#roof-plank", "uv": [ 0.0, 0.0, 18.0, 13.5 ], "rotation": 90 }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "slanted right",
|
||||||
|
"from": [ -5.0, 3.0, -1.001 ],
|
||||||
|
"to": [ 8.5, 5.25, 17.009 ],
|
||||||
|
"rotationOrigin": [ 8.0, 4.0, 8.0 ],
|
||||||
|
"rotationY": 180.0,
|
||||||
|
"rotationZ": 22.5,
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#roof-plank", "uv": [ 0.0, 0.0, 13.5, 2.25 ] },
|
||||||
|
"south": { "texture": "#roof-plank", "uv": [ 0.0, 0.0, 13.5, 2.25 ] },
|
||||||
|
"west": { "texture": "#roof-plank", "uv": [ 0.0, 0.0, 18.0, 2.25 ] },
|
||||||
|
"up": { "texture": "#roof-plank", "uv": [ 0.0, 0.0, 18.0, 13.5 ], "rotation": 90 },
|
||||||
|
"down": { "texture": "#roof-plank", "uv": [ 0.0, 0.0, 18.0, 13.5 ], "rotation": 90 }
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
{
|
||||||
|
"editor": {
|
||||||
|
"allAngles": false
|
||||||
|
},
|
||||||
|
"textureWidth": 16,
|
||||||
|
"textureHeight": 16,
|
||||||
|
"textures": {
|
||||||
|
"all": "game:block/wood/henbox/sides"
|
||||||
|
},
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"name": "TopPlate",
|
||||||
|
"from": [ 0, 14, 0 ],
|
||||||
|
"to": [ 16, 16, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#all", "uv": [ 0, 14, 16, 16 ] },
|
||||||
|
"east": { "texture": "#all", "uv": [ 0, 14, 16, 16 ] },
|
||||||
|
"south": { "texture": "#all", "uv": [ 0, 14, 16, 16 ] },
|
||||||
|
"west": { "texture": "#all", "uv": [ 0, 14, 16, 16 ] },
|
||||||
|
"up": { "texture": "#all", "uv": [ 0, 0, 16, 16 ] },
|
||||||
|
"down": { "texture": "#all", "uv": [ 0, 0, 16, 16 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LegFrontLeft",
|
||||||
|
"from": [ 0, 0, 0 ],
|
||||||
|
"to": [ 2, 14, 2 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#all", "uv": [ 2, 2, 0, 10 ] },
|
||||||
|
"east": { "texture": "#all", "uv": [ 0, 2, 2, 10 ] },
|
||||||
|
"south": { "texture": "#all", "uv": [ 0, 2, 2, 10 ] },
|
||||||
|
"west": { "texture": "#all", "uv": [ 0, 2, 2, 10 ] },
|
||||||
|
"up": { "texture": "#all", "uv": [ 0, 0, 2, 2 ] },
|
||||||
|
"down": { "texture": "#all", "uv": [ 0, 14, 2, 16 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LegFrontRight",
|
||||||
|
"from": [ 14, 0, 0 ],
|
||||||
|
"to": [ 16, 14, 2 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#all", "uv": [ 16, 2, 14, 10 ] },
|
||||||
|
"east": { "texture": "#all", "uv": [ 0, 2, 2, 10 ] },
|
||||||
|
"south": { "texture": "#all", "uv": [ 14, 2, 16, 10 ] },
|
||||||
|
"west": { "texture": "#all", "uv": [ 0, 2, 2, 10 ] },
|
||||||
|
"up": { "texture": "#all", "uv": [ 14, 0, 16, 2 ] },
|
||||||
|
"down": { "texture": "#all", "uv": [ 14, 14, 16, 16 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LegBackLeft",
|
||||||
|
"from": [ 0, 0, 14 ],
|
||||||
|
"to": [ 2, 14, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#all", "uv": [ 0, 2, 2, 10 ] },
|
||||||
|
"east": { "texture": "#all", "uv": [ 14, 2, 16, 10 ] },
|
||||||
|
"south": { "texture": "#all", "uv": [ 0, 2, 2, 10 ] },
|
||||||
|
"west": { "texture": "#all", "uv": [ 14, 2, 16, 10 ] },
|
||||||
|
"up": { "texture": "#all", "uv": [ 0, 14, 2, 16 ] },
|
||||||
|
"down": { "texture": "#all", "uv": [ 0, 0, 2, 2 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LegBackRight",
|
||||||
|
"from": [ 14, 0, 14 ],
|
||||||
|
"to": [ 16, 14, 16 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#all", "uv": [ 14, 2, 16, 10 ] },
|
||||||
|
"east": { "texture": "#all", "uv": [ 14, 2, 16, 10 ] },
|
||||||
|
"south": { "texture": "#all", "uv": [ 14, 2, 16, 10 ] },
|
||||||
|
"west": { "texture": "#all", "uv": [ 14, 2, 16, 10 ] },
|
||||||
|
"up": { "texture": "#all", "uv": [ 14, 14, 16, 16 ] },
|
||||||
|
"down": { "texture": "#all", "uv": [ 14, 0, 16, 2 ] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
{
|
||||||
|
"editor": {
|
||||||
|
"allAngles": false
|
||||||
|
},
|
||||||
|
"textureWidth": 16,
|
||||||
|
"textureHeight": 16,
|
||||||
|
"textures": {
|
||||||
|
"swarm": "game:block/soil/fertcompost"
|
||||||
|
},
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"name": "layer-1",
|
||||||
|
"from": [ 4, 4, 0 ],
|
||||||
|
"to": [ 12, 12, 0.8 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#swarm", "uv": [ 4, 4, 12, 12 ] },
|
||||||
|
"east": { "texture": "#swarm", "uv": [ 0, 4, 0.8, 12 ] },
|
||||||
|
"south": { "texture": "#swarm", "uv": [ 4, 4, 12, 12 ] },
|
||||||
|
"west": { "texture": "#swarm", "uv": [ 0, 4, 0.8, 12 ] },
|
||||||
|
"up": { "texture": "#swarm", "uv": [ 4, 0, 12, 0.8 ] },
|
||||||
|
"down": { "texture": "#swarm", "uv": [ 4, 0, 12, 0.8 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "layer-2",
|
||||||
|
"from": [ 5, 5, 0.8 ],
|
||||||
|
"to": [ 11, 11, 1.6 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#swarm", "uv": [ 5, 5, 11, 11 ] },
|
||||||
|
"east": { "texture": "#swarm", "uv": [ 0.8, 5, 1.6, 11 ] },
|
||||||
|
"south": { "texture": "#swarm", "uv": [ 5, 5, 11, 11 ] },
|
||||||
|
"west": { "texture": "#swarm", "uv": [ 0.8, 5, 1.6, 11 ] },
|
||||||
|
"up": { "texture": "#swarm", "uv": [ 5, 0.8, 11, 1.6 ] },
|
||||||
|
"down": { "texture": "#swarm", "uv": [ 5, 0.8, 11, 1.6 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "layer-3",
|
||||||
|
"from": [ 6, 6, 1.6 ],
|
||||||
|
"to": [ 10, 10, 2.4 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#swarm", "uv": [ 6, 6, 10, 10 ] },
|
||||||
|
"east": { "texture": "#swarm", "uv": [ 1.6, 6, 2.4, 10 ] },
|
||||||
|
"south": { "texture": "#swarm", "uv": [ 6, 6, 10, 10 ] },
|
||||||
|
"west": { "texture": "#swarm", "uv": [ 1.6, 6, 2.4, 10 ] },
|
||||||
|
"up": { "texture": "#swarm", "uv": [ 6, 1.6, 10, 2.4 ] },
|
||||||
|
"down": { "texture": "#swarm", "uv": [ 6, 1.6, 10, 2.4 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "layer-4",
|
||||||
|
"from": [ 7, 7, 2.4 ],
|
||||||
|
"to": [ 9, 9, 3.2 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#swarm", "uv": [ 7, 7, 9, 9 ] },
|
||||||
|
"east": { "texture": "#swarm", "uv": [ 2.4, 7, 3.2, 9 ] },
|
||||||
|
"south": { "texture": "#swarm", "uv": [ 7, 7, 9, 9 ] },
|
||||||
|
"west": { "texture": "#swarm", "uv": [ 2.4, 7, 3.2, 9 ] },
|
||||||
|
"up": { "texture": "#swarm", "uv": [ 7, 2.4, 9, 3.2 ] },
|
||||||
|
"down": { "texture": "#swarm", "uv": [ 7, 2.4, 9, 3.2 ] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
{
|
||||||
|
"editor": {
|
||||||
|
"allAngles": true
|
||||||
|
},
|
||||||
|
"textureWidth": 16,
|
||||||
|
"textureHeight": 16,
|
||||||
|
"textures": {
|
||||||
|
"frame": "game:block/wood-generic",
|
||||||
|
"center": "game:block/linen"
|
||||||
|
},
|
||||||
|
"elements": [
|
||||||
|
{
|
||||||
|
"name": "Frame",
|
||||||
|
"from": [ 3, 0.45, 5 ],
|
||||||
|
"to": [ 13, 0.55, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"up": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] },
|
||||||
|
"down": { "texture": "#center", "uv": [ 3, 5, 13, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "TopBorder",
|
||||||
|
"from": [ 2, 0, 11 ],
|
||||||
|
"to": [ 14, 1, 12 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 11, 0, 12, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 11, 14, 12 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "BottomBorder",
|
||||||
|
"from": [ 2, 0, 4 ],
|
||||||
|
"to": [ 14, 1, 5 ],
|
||||||
|
"faces": {
|
||||||
|
"north": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"south": { "texture": "#frame", "uv": [ 2, 0, 14, 1 ] },
|
||||||
|
"east": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 4, 0, 5, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 4, 14, 5 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "LeftBorder",
|
||||||
|
"from": [ 2, 0, 5 ],
|
||||||
|
"to": [ 3, 1, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 2, 5, 3, 11 ] }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "RightBorder",
|
||||||
|
"from": [ 13, 0, 5 ],
|
||||||
|
"to": [ 14, 1, 11 ],
|
||||||
|
"faces": {
|
||||||
|
"east": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"west": { "texture": "#frame", "uv": [ 5, 0, 11, 1 ] },
|
||||||
|
"up": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] },
|
||||||
|
"down": { "texture": "#frame", "uv": [ 13, 5, 14, 11 ] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
BIN
OrekiWoofsBeehives/modicon.png
Normal file
BIN
OrekiWoofsBeehives/modicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
14
OrekiWoofsBeehives/modinfo.json
Normal file
14
OrekiWoofsBeehives/modinfo.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://moddbcdn.vintagestory.at/schema/modinfo.latest.json",
|
||||||
|
"type": "Code",
|
||||||
|
"modid": "orekiwoofsbeehives",
|
||||||
|
"name": "OrekiWoof's Simple Immersive Beehive",
|
||||||
|
"authors": [
|
||||||
|
"OrekiWoof"
|
||||||
|
],
|
||||||
|
"description": "Beehive with 8 slots for honey frames. Boosts your plants.",
|
||||||
|
"version": "2.0.0-dev.6",
|
||||||
|
"dependencies": {
|
||||||
|
"game": "1.21.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
99
OrekiWoofsBees.Common/Configs/ChatCommands.cs
Normal file
99
OrekiWoofsBees.Common/Configs/ChatCommands.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.Config;
|
||||||
|
using Vintagestory.API.Server;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBees.Common.Configs;
|
||||||
|
|
||||||
|
public static class ConfigCommands
|
||||||
|
{
|
||||||
|
public static void Register<T>(
|
||||||
|
IChatCommandApi chatApi,
|
||||||
|
string commandName,
|
||||||
|
string langDomain,
|
||||||
|
bool serverSide,
|
||||||
|
Func<T> getInstance,
|
||||||
|
Action<bool> saveConfig)
|
||||||
|
{
|
||||||
|
Register(chatApi.Create(commandName), chatApi, langDomain, serverSide, getInstance, saveConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Register<T>(
|
||||||
|
IChatCommand builder,
|
||||||
|
IChatCommandApi chatApi,
|
||||||
|
string langDomain,
|
||||||
|
bool serverSide,
|
||||||
|
Func<T> getInstance,
|
||||||
|
Action<bool> saveConfig)
|
||||||
|
{
|
||||||
|
if (serverSide)
|
||||||
|
builder = builder.RequiresPrivilege(Privilege.controlserver);
|
||||||
|
|
||||||
|
var p = chatApi.Parsers;
|
||||||
|
foreach (var prop in typeof(T).GetProperties())
|
||||||
|
{
|
||||||
|
var attr = prop.GetCustomAttribute<ConfigCommandAttribute>();
|
||||||
|
if (attr == null || attr.ServerSide != serverSide) continue;
|
||||||
|
|
||||||
|
var name = prop.Name;
|
||||||
|
var descKey = $"{langDomain}:config-desc-{name}";
|
||||||
|
|
||||||
|
OnCommandDelegate handler;
|
||||||
|
ICommandArgumentParser parser;
|
||||||
|
|
||||||
|
if (prop.PropertyType == typeof(int))
|
||||||
|
{
|
||||||
|
parser = p.OptionalIntRange("value", (int)attr.Min, (int)attr.Max);
|
||||||
|
handler = args => HandleOptional(args, name, descKey,
|
||||||
|
() => $"{prop.GetValue(getInstance())}",
|
||||||
|
() => { prop.SetValue(getInstance(), (int)args.Parsers[0].GetValue()!); saveConfig(serverSide); },
|
||||||
|
() => args.Parsers[0].GetValue() is int);
|
||||||
|
}
|
||||||
|
else if (prop.PropertyType == typeof(float))
|
||||||
|
{
|
||||||
|
parser = ParserExtensions.OptionalFloatRange("value", (float)attr.Min, (float)attr.Max);
|
||||||
|
handler = args => HandleOptional(args, name, descKey,
|
||||||
|
() => $"{(float)prop.GetValue(getInstance())!:G}",
|
||||||
|
() => { prop.SetValue(getInstance(), (float)args.Parsers[0].GetValue()!); saveConfig(serverSide); },
|
||||||
|
() => args.Parsers[0].GetValue() is float);
|
||||||
|
}
|
||||||
|
else if (prop.PropertyType == typeof(bool))
|
||||||
|
{
|
||||||
|
parser = p.OptionalBool("value");
|
||||||
|
handler = args => HandleOptional(args, name, descKey,
|
||||||
|
() => $"{prop.GetValue(getInstance())}",
|
||||||
|
() => { prop.SetValue(getInstance(), (bool)args.Parsers[0].GetValue()!); saveConfig(serverSide); },
|
||||||
|
() => args.Parsers[0].GetValue() is bool);
|
||||||
|
}
|
||||||
|
else if (prop.PropertyType == typeof(string))
|
||||||
|
{
|
||||||
|
parser = p.OptionalWordRange("value", attr.AllowedValues);
|
||||||
|
handler = args => HandleOptional(args, name, descKey,
|
||||||
|
() => $"{prop.GetValue(getInstance())}",
|
||||||
|
() => { prop.SetValue(getInstance(), (string)args.Parsers[0].GetValue()!); saveConfig(serverSide); },
|
||||||
|
() => args.Parsers[0].GetValue() is string);
|
||||||
|
}
|
||||||
|
else continue;
|
||||||
|
|
||||||
|
builder
|
||||||
|
.BeginSubCommand(name)
|
||||||
|
.WithDescription(Lang.Get(descKey))
|
||||||
|
.WithArgs(parser)
|
||||||
|
.HandleWith(handler)
|
||||||
|
.EndSubCommand();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TextCommandResult HandleOptional(
|
||||||
|
TextCommandCallingArgs args, string name, string descKey,
|
||||||
|
Func<string> formatValue, Action applyAndSave, Func<bool> canParse)
|
||||||
|
{
|
||||||
|
if (args.Parsers[0].IsMissing)
|
||||||
|
return TextCommandResult.Success($"{Lang.Get(descKey)}\n{name}={formatValue()}");
|
||||||
|
if (!canParse())
|
||||||
|
return TextCommandResult.Error("Couldn't parse.");
|
||||||
|
applyAndSave();
|
||||||
|
return TextCommandResult.Success($"{name}={formatValue()}");
|
||||||
|
}
|
||||||
|
}
|
||||||
23
OrekiWoofsBees.Common/Configs/ConfigCommandAttribute.cs
Normal file
23
OrekiWoofsBees.Common/Configs/ConfigCommandAttribute.cs
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBees.Common.Configs;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Marks a Config property as having a /beehives or .beehives chat command.
|
||||||
|
/// The registration loop in ChatCommands.cs picks these up.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public class ConfigCommandAttribute(bool serverSide) : Attribute
|
||||||
|
{
|
||||||
|
/// <summary>True = registered as a server command and broadcasts on change.</summary>
|
||||||
|
public bool ServerSide { get; } = serverSide;
|
||||||
|
|
||||||
|
/// <summary>Inclusive</summary>
|
||||||
|
public double Min { get; set; }
|
||||||
|
|
||||||
|
/// <summary>Inclusive</summary>
|
||||||
|
public double Max { get; set; }
|
||||||
|
|
||||||
|
/// <remarks>Allowed values for string enum properties (uses OptionalWordRange).</remarks>
|
||||||
|
public string[] AllowedValues { get; set; } = [];
|
||||||
|
}
|
||||||
16
OrekiWoofsBees.Common/Configs/ParserExtensions.cs
Normal file
16
OrekiWoofsBees.Common/Configs/ParserExtensions.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
using Vintagestory.API.Common;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBees.Common.Configs;
|
||||||
|
|
||||||
|
public static class ParserExtensions
|
||||||
|
{
|
||||||
|
public static FloatArgParser OptionalFloatRange(string argName, float min, float max)
|
||||||
|
{
|
||||||
|
return new FloatArgParser(argName, min, max, isMandatoryArg: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DoubleArgParser OptionalDoubleRange(string argName, double min, double max)
|
||||||
|
{
|
||||||
|
return new DoubleArgParser(argName, min, max, isMandatoryArg: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
40
OrekiWoofsBees.Common/IPlantPositionRegistry.cs
Normal file
40
OrekiWoofsBees.Common/IPlantPositionRegistry.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBees.Common;
|
||||||
|
|
||||||
|
public interface IPlantPositionRegistry
|
||||||
|
{
|
||||||
|
ICoreAPI? Api { get; }
|
||||||
|
|
||||||
|
event Action<BlockPos, int>? CropEvent;
|
||||||
|
event Action<BlockPos, int>? FlowerEvent;
|
||||||
|
|
||||||
|
void AddPlantPosition(BlockPos pos, Block block);
|
||||||
|
|
||||||
|
int CountBeehivesInRadius(BlockPos pos, int radius);
|
||||||
|
|
||||||
|
(
|
||||||
|
int FlowerCount,
|
||||||
|
int CropCount,
|
||||||
|
float InitialScanProgress,
|
||||||
|
float RescanProgress
|
||||||
|
)
|
||||||
|
GetPlantCountsNearPosition(BlockPos hivePos, int radius);
|
||||||
|
|
||||||
|
(
|
||||||
|
IEnumerable<BlockPos> Flowers,
|
||||||
|
IEnumerable<BlockPos> Crops,
|
||||||
|
float InitialScanProgress,
|
||||||
|
float RescanProgress
|
||||||
|
)
|
||||||
|
GetPlantsNearPosition(BlockPos hivePos, int radius);
|
||||||
|
|
||||||
|
void RegisterBeehive(BlockPos pos, int radius);
|
||||||
|
|
||||||
|
void RemovePlantPosition(BlockPos pos, Block block);
|
||||||
|
|
||||||
|
void UnregisterBeehive(BlockPos pos);
|
||||||
|
}
|
||||||
62
OrekiWoofsBees.Common/OrekiWoofsBees.Common.csproj
Normal file
62
OrekiWoofsBees.Common/OrekiWoofsBees.Common.csproj
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<Configurations>Debug;Release;Debug22</Configurations>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)' == 'Debug22'">
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<VINTAGE_STORY>$(VINTAGE_STORY_22PRE2)</VINTAGE_STORY>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="VintagestoryAPI">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/VintagestoryAPI.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VSSurvivalMod">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/Mods/VSSurvivalMod.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VSEssentials">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/Mods/VSEssentials.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VSCreativeMod">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/Mods/VSCreativeMod.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Newtonsoft.Json">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/Lib/Newtonsoft.Json.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="0Harmony">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/Lib/0Harmony.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="VintagestoryLib">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/VintagestoryLib.dll</HintPath>
|
||||||
|
<Private>false</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="protobuf-net">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/Lib/protobuf-net.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="cairo-sharp">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/Lib/cairo-sharp.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Microsoft.Data.Sqlite">
|
||||||
|
<HintPath>$(VINTAGE_STORY)/Lib/Microsoft.Data.Sqlite.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="configlib">
|
||||||
|
<HintPath>E:\Code\VintageStory\configlib_1.10.14\configlib.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
32
OrekiWoofsBees.Common/Overlaps.cs
Normal file
32
OrekiWoofsBees.Common/Overlaps.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBees.Common;
|
||||||
|
|
||||||
|
public static class Overlaps
|
||||||
|
{
|
||||||
|
public static bool IsWithinSphericalRadius(BlockPos center, StructVec3i pos, int radius)
|
||||||
|
{
|
||||||
|
return IsWithinSphericalRadiusSq(center, pos, radius * radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsWithinSphericalRadiusSq(BlockPos center, StructVec3i pos, int radiusSq)
|
||||||
|
{
|
||||||
|
int dx = pos.X - center.X;
|
||||||
|
int dy = pos.Y - center.Y;
|
||||||
|
int dz = pos.Z - center.Z;
|
||||||
|
return dx * dx + dy * dy + dz * dz <= radiusSq;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsWithinSphericalRadius(StructVec3i center, StructVec3i pos, int radius)
|
||||||
|
{
|
||||||
|
return IsWithinSphericalRadiusSq(center, pos, radius * radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsWithinSphericalRadiusSq(StructVec3i center, StructVec3i pos, int radiusSq)
|
||||||
|
{
|
||||||
|
int dx = pos.X - center.X;
|
||||||
|
int dy = pos.Y - center.Y;
|
||||||
|
int dz = pos.Z - center.Z;
|
||||||
|
return dx * dx + dy * dy + dz * dz <= radiusSq;
|
||||||
|
}
|
||||||
|
}
|
||||||
445
OrekiWoofsBees.Common/PlantPositionRegistryModSystem2.cs
Normal file
445
OrekiWoofsBees.Common/PlantPositionRegistryModSystem2.cs
Normal file
@@ -0,0 +1,445 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
using Vintagestory.GameContent;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBees.Common;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// tracks plant positions for all registered beehives.
|
||||||
|
/// Instead of each beehive scanning its radius every tick, this registry
|
||||||
|
/// incrementally scans blocks across all beehives to keep performance consistent
|
||||||
|
/// </summary>
|
||||||
|
public class PlantPositionRegistryModSystem2 : ModSystem, IPlantPositionRegistry
|
||||||
|
{
|
||||||
|
private const int default_blocks_per_tick = 20;
|
||||||
|
private const int tick_interval_ms = 20;
|
||||||
|
|
||||||
|
private static readonly Dictionary<int, ScanOffsetTable> offsetTables = [];
|
||||||
|
|
||||||
|
private long? tickListenerId;
|
||||||
|
private readonly Dictionary<StructVec3i, BeehiveScanCursor> beehives = [];
|
||||||
|
private readonly HashSet<StructVec3i> flowerPositions = [];
|
||||||
|
private readonly HashSet<StructVec3i> cropPositions = [];
|
||||||
|
|
||||||
|
// blocks below this are skipped
|
||||||
|
private readonly Dictionary<(int X, int Z), int> soilFloorCache = [];
|
||||||
|
|
||||||
|
private int lastScannedBeehiveIndex = 0;
|
||||||
|
private int blocksPerTick = default_blocks_per_tick;
|
||||||
|
|
||||||
|
public ICoreAPI? Api { get; private set; }
|
||||||
|
|
||||||
|
public int BlocksPerTick
|
||||||
|
{
|
||||||
|
get => blocksPerTick;
|
||||||
|
set => blocksPerTick = Math.Clamp(value, 0, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public event Action<BlockPos, int>? FlowerEvent;
|
||||||
|
public event Action<BlockPos, int>? CropEvent;
|
||||||
|
|
||||||
|
public override double ExecuteOrder() => 0.10;
|
||||||
|
|
||||||
|
public override void Start(ICoreAPI api)
|
||||||
|
{
|
||||||
|
Api = api;
|
||||||
|
if (api.Side.IsServer())
|
||||||
|
tickListenerId = api.Event.RegisterGameTickListener(OnTick, tick_interval_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Dispose()
|
||||||
|
{
|
||||||
|
if (Api != null && tickListenerId.HasValue)
|
||||||
|
Api.Event.UnregisterGameTickListener(tickListenerId.Value);
|
||||||
|
beehives.Clear();
|
||||||
|
flowerPositions.Clear();
|
||||||
|
cropPositions.Clear();
|
||||||
|
FlowerEvent = null;
|
||||||
|
CropEvent = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterBeehive(BlockPos pos, int radius)
|
||||||
|
{
|
||||||
|
var key = StructVec3i.FromBlockPos(pos);
|
||||||
|
if (beehives.ContainsKey(key))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!offsetTables.ContainsKey(radius))
|
||||||
|
offsetTables[radius] = new ScanOffsetTable(radius);
|
||||||
|
|
||||||
|
beehives[key] = new BeehiveScanCursor(pos, radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UnregisterBeehive(BlockPos pos)
|
||||||
|
{
|
||||||
|
var key = StructVec3i.FromBlockPos(pos);
|
||||||
|
beehives.Remove(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (
|
||||||
|
IEnumerable<BlockPos> Flowers,
|
||||||
|
IEnumerable<BlockPos> Crops,
|
||||||
|
float InitialScanProgress,
|
||||||
|
float RescanProgress
|
||||||
|
) GetPlantsNearPosition(BlockPos hivePos, int radius)
|
||||||
|
{
|
||||||
|
var key = StructVec3i.FromBlockPos(hivePos);
|
||||||
|
|
||||||
|
var flowers = flowerPositions
|
||||||
|
.Where(p => Overlaps.IsWithinSphericalRadius(hivePos, p, radius))
|
||||||
|
.Select(p => new BlockPos(p.X, p.Y, p.Z, hivePos.dimension));
|
||||||
|
|
||||||
|
var crops = cropPositions
|
||||||
|
.Where(p => Overlaps.IsWithinSphericalRadius(hivePos, p, radius))
|
||||||
|
.Select(p => new BlockPos(p.X, p.Y, p.Z, hivePos.dimension));
|
||||||
|
|
||||||
|
float initialProgress = 1.0f;
|
||||||
|
float rescanProgress = 0.0f;
|
||||||
|
if (!beehives.TryGetValue(key, out var cursor))
|
||||||
|
return (flowers, crops, initialProgress, rescanProgress);
|
||||||
|
|
||||||
|
var table = offsetTables[cursor.Radius];
|
||||||
|
if (table.Count > 0)
|
||||||
|
{
|
||||||
|
initialProgress = Math.Min(1.0f, (float)cursor.BlocksCheckedCount / table.Count);
|
||||||
|
rescanProgress = cursor.GetRescanProgress(offsetTables);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (flowers, crops, initialProgress, rescanProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public (int FlowerCount, int CropCount, float InitialScanProgress, float RescanProgress) GetPlantCountsNearPosition(BlockPos hivePos, int radius)
|
||||||
|
{
|
||||||
|
int flowers = flowerPositions.Count(p => Overlaps.IsWithinSphericalRadius(hivePos, p, radius));
|
||||||
|
int crops = cropPositions.Count(p => Overlaps.IsWithinSphericalRadius(hivePos, p, radius));
|
||||||
|
|
||||||
|
float initialProgress = 0.0f;
|
||||||
|
float rescanProgress = 0.0f;
|
||||||
|
var key = StructVec3i.FromBlockPos(hivePos);
|
||||||
|
if (!beehives.TryGetValue(key, out var cursor))
|
||||||
|
return (flowers, crops, initialProgress, rescanProgress);
|
||||||
|
|
||||||
|
var table = offsetTables[cursor.Radius];
|
||||||
|
if (table.Count > 0)
|
||||||
|
{
|
||||||
|
initialProgress = Math.Min(1.0f, (float)cursor.BlocksCheckedCount / table.Count);
|
||||||
|
rescanProgress = cursor.GetRescanProgress(offsetTables);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (flowers, crops, initialProgress, rescanProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddPlantPosition(BlockPos pos, Block block)
|
||||||
|
{
|
||||||
|
if (Api is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var structPos = new StructVec3i(pos.X, pos.Y, pos.Z);
|
||||||
|
|
||||||
|
if (PlantRecognitionUtilities.IsCrop(block))
|
||||||
|
{
|
||||||
|
cropPositions.Add(structPos);
|
||||||
|
flowerPositions.Remove(structPos);
|
||||||
|
CropEvent?.Invoke(pos, 1);
|
||||||
|
}
|
||||||
|
else if (PlantRecognitionUtilities.IsFlower(block, Api.World.BlockAccessor, pos))
|
||||||
|
{
|
||||||
|
flowerPositions.Add(structPos);
|
||||||
|
cropPositions.Remove(structPos);
|
||||||
|
FlowerEvent?.Invoke(pos, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemovePlantPosition(BlockPos pos, Block block)
|
||||||
|
{
|
||||||
|
if (Api is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var structPos = new StructVec3i(pos.X, pos.Y, pos.Z);
|
||||||
|
flowerPositions.Remove(structPos);
|
||||||
|
cropPositions.Remove(structPos);
|
||||||
|
|
||||||
|
if (PlantRecognitionUtilities.IsCrop(block))
|
||||||
|
CropEvent?.Invoke(pos, -1);
|
||||||
|
else if (PlantRecognitionUtilities.IsFlower(block, Api.World.BlockAccessor, pos))
|
||||||
|
FlowerEvent?.Invoke(pos, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CountBeehivesInRadius(BlockPos pos, int radius)
|
||||||
|
{
|
||||||
|
var plantPos = StructVec3i.FromBlockPos(pos);
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
foreach (var (beehivePos, _) in beehives)
|
||||||
|
{
|
||||||
|
if (Overlaps.IsWithinSphericalRadius(beehivePos, plantPos, radius))
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTick(float dt)
|
||||||
|
{
|
||||||
|
if (Api is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
if (beehives.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var accessor = Api.World.BlockAccessor;
|
||||||
|
|
||||||
|
for (int i = 0; i < BlocksPerTick; i++)
|
||||||
|
{
|
||||||
|
var nextBlock = GetNextBlockToCheck();
|
||||||
|
if (nextBlock == null)
|
||||||
|
break;
|
||||||
|
|
||||||
|
var (cursor, blockPos) = nextBlock.Value;
|
||||||
|
|
||||||
|
// check if this position is below the soil floor for this X/Z
|
||||||
|
var xz = (blockPos.X, blockPos.Z);
|
||||||
|
if (soilFloorCache.TryGetValue(xz, out int soilFloorY) && blockPos.Y < soilFloorY)
|
||||||
|
{
|
||||||
|
// it's below the soil floor, skip
|
||||||
|
cursor.Advance();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor.Advance();
|
||||||
|
|
||||||
|
var block = accessor.GetBlock(blockPos);
|
||||||
|
var structPos = new StructVec3i(blockPos.X, blockPos.Y, blockPos.Z);
|
||||||
|
|
||||||
|
if (block == null || block.BlockId == 0)
|
||||||
|
{
|
||||||
|
// block is air or unloaded - remove from caches if present
|
||||||
|
flowerPositions.Remove(structPos);
|
||||||
|
cropPositions.Remove(structPos);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if this is soil with soil below - mark as soil floor
|
||||||
|
if (block is BlockSoil)
|
||||||
|
{
|
||||||
|
var blockBelow = accessor.GetBlock(blockPos.DownCopy());
|
||||||
|
if (blockBelow is BlockSoil)
|
||||||
|
{
|
||||||
|
// found soil floor - record it
|
||||||
|
bool isNewFloor = false;
|
||||||
|
if (!soilFloorCache.TryGetValue(xz, out int existingFloor) || blockPos.Y < existingFloor)
|
||||||
|
{
|
||||||
|
soilFloorCache[xz] = blockPos.Y;
|
||||||
|
isNewFloor = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNewFloor)
|
||||||
|
cursor.CountAndSkipBlocksBelowY(blockPos.Y, xz, offsetTables);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PlantRecognitionUtilities.IsCrop(block))
|
||||||
|
{
|
||||||
|
cropPositions.Add(structPos);
|
||||||
|
flowerPositions.Remove(structPos);
|
||||||
|
}
|
||||||
|
else if (PlantRecognitionUtilities.IsFlower(block, accessor, blockPos))
|
||||||
|
{
|
||||||
|
flowerPositions.Add(structPos);
|
||||||
|
cropPositions.Remove(structPos);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
flowerPositions.Remove(structPos);
|
||||||
|
cropPositions.Remove(structPos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stopwatch.Stop();
|
||||||
|
if (stopwatch.Elapsed.TotalSeconds > 0.2)
|
||||||
|
{
|
||||||
|
Mod.Logger.Warning($"{nameof(PlantPositionRegistryModSystem2)} {nameof(OnTick)} took {stopwatch.Elapsed.TotalSeconds:F2}s (beehives: {beehives.Count}).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 1. pick the beehive with the least blocks checked in its radius
|
||||||
|
/// 2. pick the closest horizontal space vertically
|
||||||
|
/// 3. check the next closest block to the beehive in this horizontal space
|
||||||
|
/// </summary>
|
||||||
|
private (BeehiveScanCursor Cursor, BlockPos BlockPos)? GetNextBlockToCheck()
|
||||||
|
{
|
||||||
|
if (beehives.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
bool allCompleted = true;
|
||||||
|
foreach (var cursor in beehives.Values)
|
||||||
|
{
|
||||||
|
var table = offsetTables[cursor.Radius];
|
||||||
|
if (cursor.BlocksCheckedCount < table.Count)
|
||||||
|
{
|
||||||
|
allCompleted = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BeehiveScanCursor? bestCursor = null;
|
||||||
|
|
||||||
|
if (!allCompleted)
|
||||||
|
{
|
||||||
|
var lowestRelativeProgress = float.MaxValue;
|
||||||
|
foreach (var cursor in beehives.Values)
|
||||||
|
{
|
||||||
|
var table = offsetTables[cursor.Radius];
|
||||||
|
if (cursor.BlocksCheckedCount >= table.Count)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var relativeProgress = (float)cursor.BlocksCheckedCount / table.Count;
|
||||||
|
if (relativeProgress < lowestRelativeProgress)
|
||||||
|
{
|
||||||
|
lowestRelativeProgress = relativeProgress;
|
||||||
|
bestCursor = cursor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var beehivesList = beehives.Values.ToList();
|
||||||
|
lastScannedBeehiveIndex = (lastScannedBeehiveIndex + 1) % beehivesList.Count;
|
||||||
|
bestCursor = beehivesList[lastScannedBeehiveIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bestCursor == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (bestCursor.IsStartingNewCycle())
|
||||||
|
soilFloorCache.Clear();
|
||||||
|
|
||||||
|
var blockPos = bestCursor.GetCurrentBlockPos(offsetTables);
|
||||||
|
return (bestCursor, blockPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BeehiveScanCursor(BlockPos hivePos, int radius)
|
||||||
|
{
|
||||||
|
public BlockPos HivePos { get; } = hivePos;
|
||||||
|
public int Radius { get; } = radius;
|
||||||
|
public int BlocksCheckedCount { get; private set; } = 0;
|
||||||
|
|
||||||
|
private int currentIndex = 0;
|
||||||
|
|
||||||
|
public BlockPos GetCurrentBlockPos(Dictionary<int, ScanOffsetTable> tables)
|
||||||
|
{
|
||||||
|
var table = tables[Radius];
|
||||||
|
var (X, Y, Z) = table.GetOffset(currentIndex);
|
||||||
|
return new BlockPos(
|
||||||
|
HivePos.X + X,
|
||||||
|
HivePos.Y + Y,
|
||||||
|
HivePos.Z + Z,
|
||||||
|
HivePos.dimension
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Advance()
|
||||||
|
{
|
||||||
|
var table = offsetTables[Radius];
|
||||||
|
currentIndex++;
|
||||||
|
|
||||||
|
if (currentIndex >= table.Count)
|
||||||
|
currentIndex = 0;
|
||||||
|
|
||||||
|
if (BlocksCheckedCount < table.Count)
|
||||||
|
BlocksCheckedCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsStartingNewCycle()
|
||||||
|
{
|
||||||
|
return currentIndex == 0 && BlocksCheckedCount >= offsetTables[Radius].Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetCurrentIndex()
|
||||||
|
{
|
||||||
|
return currentIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CountAndSkipBlocksBelowY(int floorY, (int X, int Z) xz, Dictionary<int, ScanOffsetTable> tables)
|
||||||
|
{
|
||||||
|
var table = tables[Radius];
|
||||||
|
int skippedCount = 0;
|
||||||
|
|
||||||
|
for (int i = currentIndex + 1; i < table.Count; i++)
|
||||||
|
{
|
||||||
|
var (offsetX, offsetY, offsetZ) = table.GetOffset(i);
|
||||||
|
int worldX = HivePos.X + offsetX;
|
||||||
|
int worldZ = HivePos.Z + offsetZ;
|
||||||
|
int worldY = HivePos.Y + offsetY;
|
||||||
|
|
||||||
|
if (worldX == xz.X && worldZ == xz.Z && worldY < floorY)
|
||||||
|
{
|
||||||
|
skippedCount++;
|
||||||
|
if (BlocksCheckedCount < table.Count)
|
||||||
|
BlocksCheckedCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public float GetRescanProgress(Dictionary<int, ScanOffsetTable> tables)
|
||||||
|
{
|
||||||
|
var table = tables[Radius];
|
||||||
|
if (table.Count == 0)
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
if (BlocksCheckedCount < table.Count)
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
return (float)currentIndex / table.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// shared table of offsets for a given radius, sorted by priority.
|
||||||
|
/// only one instance per radius is created and shared across all beehives.
|
||||||
|
/// </summary>
|
||||||
|
private class ScanOffsetTable
|
||||||
|
{
|
||||||
|
private readonly (int X, int Y, int Z)[] offsets;
|
||||||
|
public int Count => offsets.Length;
|
||||||
|
|
||||||
|
public ScanOffsetTable(int radius)
|
||||||
|
{
|
||||||
|
int radiusSq = radius * radius;
|
||||||
|
var offsets = new List<(int X, int Y, int Z, int YDist, int HorizontalDistSq)>();
|
||||||
|
|
||||||
|
for (int dy = -radius; dy <= radius; dy++)
|
||||||
|
{
|
||||||
|
for (int dx = -radius; dx <= radius; dx++)
|
||||||
|
{
|
||||||
|
for (int dz = -radius; dz <= radius; dz++)
|
||||||
|
{
|
||||||
|
int distSq = dx * dx + dy * dy + dz * dz;
|
||||||
|
if (distSq <= radiusSq)
|
||||||
|
{
|
||||||
|
int yDist = Math.Abs(dy);
|
||||||
|
int horizontalDistSq = dx * dx + dz * dz;
|
||||||
|
offsets.Add((dx, dy, dz, yDist, horizontalDistSq));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.offsets = [.. offsets
|
||||||
|
.OrderBy(o => o.YDist)
|
||||||
|
.ThenBy(o => o.HorizontalDistSq)
|
||||||
|
.Select(o => (o.X, o.Y, o.Z))];
|
||||||
|
}
|
||||||
|
|
||||||
|
public (int X, int Y, int Z) GetOffset(int index)
|
||||||
|
{
|
||||||
|
return offsets[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
45
OrekiWoofsBees.Common/PlantRecognitionUtilities.cs
Normal file
45
OrekiWoofsBees.Common/PlantRecognitionUtilities.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
using Vintagestory.GameContent;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBees.Common;
|
||||||
|
|
||||||
|
public static class PlantRecognitionUtilities
|
||||||
|
{
|
||||||
|
public static bool IsCrop(Block block)
|
||||||
|
{
|
||||||
|
return block is BlockCrop;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsFlower(Block block, IBlockAccessor accessor, BlockPos pos)
|
||||||
|
{
|
||||||
|
if (block.FirstCodePart() == "flower")
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (block is BlockPlantContainer)
|
||||||
|
{
|
||||||
|
var plantContainer = block.GetBlockEntity<BlockEntityPlantContainer?>(pos);
|
||||||
|
if (plantContainer is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var contents = plantContainer.GetContents();
|
||||||
|
if (contents is null)
|
||||||
|
return false;
|
||||||
|
if (contents.Block?.FirstCodePart() == "flower")
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block is BlockBerryBush && accessor.GetBlockEntity(pos) is BlockEntityBerryBush blockEntityBerryBush)
|
||||||
|
return blockEntityBerryBush.IsFlowering;
|
||||||
|
|
||||||
|
if (block is BlockFruitTreePart && accessor.GetBlockEntity(pos) is BlockEntityFruitTreeFoliage fruitTreeFoliage)
|
||||||
|
return fruitTreeFoliage.FoliageState == EnumFoliageState.Flowering;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsPlant(Block block, IBlockAccessor accessor, BlockPos pos)
|
||||||
|
{
|
||||||
|
return IsCrop(block) || IsFlower(block, accessor, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
8
OrekiWoofsBees.Common/StructVec3i.cs
Normal file
8
OrekiWoofsBees.Common/StructVec3i.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBees.Common;
|
||||||
|
|
||||||
|
public readonly record struct StructVec3i(int X, int Y, int Z)
|
||||||
|
{
|
||||||
|
public static StructVec3i FromBlockPos(BlockPos pos) => new(pos.X, pos.Y, pos.Z);
|
||||||
|
}
|
||||||
8
OrekiWoofsBees.Common/SwarmState.cs
Normal file
8
OrekiWoofsBees.Common/SwarmState.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace OrekiWoofsBees.Common;
|
||||||
|
|
||||||
|
public enum SwarmState
|
||||||
|
{
|
||||||
|
BuildingSwarm,
|
||||||
|
HangingOut,
|
||||||
|
MigratingToNewHive,
|
||||||
|
}
|
||||||
10
OrekiWoofsBees.Common/VectorConversionUtils.cs
Normal file
10
OrekiWoofsBees.Common/VectorConversionUtils.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace OrekiWoofsBees.Common;
|
||||||
|
|
||||||
|
public static class VectorConversionUtils
|
||||||
|
{
|
||||||
|
public static Vector3 ToVector3(this Vec3f v) => new(v.X, v.Y, v.Z);
|
||||||
|
public static Vec3f ToVec3f(this Vector3 v) => new(v.X, v.Y, v.Z);
|
||||||
|
}
|
||||||
1
OrekiWoofsBees.Full/.gitignore
vendored
Normal file
1
OrekiWoofsBees.Full/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Mods/
|
||||||
38
OrekiWoofsBees.Full/OrekiWoofsBees.Full.csproj
Normal file
38
OrekiWoofsBees.Full/OrekiWoofsBees.Full.csproj
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
|
<Configurations>Debug;Release;Debug22</Configurations>
|
||||||
|
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||||
|
<DisableFastUpToDateCheck>true</DisableFastUpToDateCheck>
|
||||||
|
<VintageStoryDataPathArgs></VintageStoryDataPathArgs>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)' == 'Debug22'">
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<VintageStoryDataPathArgs> --dataPath "..\VintageStoryDataBeehives\Data22"</VintageStoryDataPathArgs>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<Target Name="BuildBothMods" BeforeTargets="Build">
|
||||||
|
<Exec Command="dotnet build "$(ProjectDir)../OrekiWoofsBeehives/OrekiWoofsBeehives.csproj" -c $(Configuration)" />
|
||||||
|
<Exec Command="dotnet build "$(ProjectDir)../RoamingBees/RoamingBees/RoamingBees.csproj" -c $(Configuration)" />
|
||||||
|
|
||||||
|
<RemoveDir Directories="$(ProjectDir)Mods/orekiwoofsbeehives" />
|
||||||
|
<RemoveDir Directories="$(ProjectDir)Mods/roamingbees" />
|
||||||
|
<Delete Files="$(ProjectDir)Mods/orekiwoofsbeehives_v*.zip;$(ProjectDir)Mods/roamingbees_v*.zip" />
|
||||||
|
|
||||||
|
<MakeDir Directories="$(ProjectDir)Mods/orekiwoofsbeehives" />
|
||||||
|
<MakeDir Directories="$(ProjectDir)Mods/roamingbees" />
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<_BeehivesFiles Include="$(ProjectDir)../OrekiWoofsBeehives/bin/$(Configuration)/Mods/orekiwoofsbeehives/**/*" />
|
||||||
|
<_RoamingBeesFiles Include="$(ProjectDir)../RoamingBees/RoamingBees/bin/$(Configuration)/Mods/roamingbees/**/*" />
|
||||||
|
<_BeehivesAssets Include="$(ProjectDir)../OrekiWoofsBeehives/assets/**/*" />
|
||||||
|
<_RoamingBeesAssets Include="$(ProjectDir)../RoamingBees/RoamingBees/assets/**/*" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<Copy SourceFiles="@(_BeehivesFiles)" DestinationFolder="$(ProjectDir)Mods/orekiwoofsbeehives/%(RecursiveDir)" SkipUnchangedFiles="true" />
|
||||||
|
<Copy SourceFiles="@(_RoamingBeesFiles)" DestinationFolder="$(ProjectDir)Mods/roamingbees/%(RecursiveDir)" SkipUnchangedFiles="true" />
|
||||||
|
<Copy SourceFiles="@(_BeehivesAssets)" DestinationFolder="$(ProjectDir)Mods/orekiwoofsbeehives/assets/%(RecursiveDir)" SkipUnchangedFiles="true" />
|
||||||
|
<Copy SourceFiles="@(_RoamingBeesAssets)" DestinationFolder="$(ProjectDir)Mods/roamingbees/assets/%(RecursiveDir)" SkipUnchangedFiles="true" />
|
||||||
|
</Target>
|
||||||
|
</Project>
|
||||||
16
OrekiWoofsBees.Full/Properties/launchSettings.json
Normal file
16
OrekiWoofsBees.Full/Properties/launchSettings.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"Client": {
|
||||||
|
"commandName": "Executable",
|
||||||
|
"executablePath": "dotnet",
|
||||||
|
"commandLineArgs": "\"$(VINTAGE_STORY)/Vintagestory.dll\" --tracelog --addModPath \"$(ProjectDir)/Mods\"$(VintageStoryDataPathArgs)",
|
||||||
|
"workingDirectory": "$(VINTAGE_STORY)"
|
||||||
|
},
|
||||||
|
"Server": {
|
||||||
|
"commandName": "Executable",
|
||||||
|
"executablePath": "dotnet",
|
||||||
|
"commandLineArgs": "\"$(VINTAGE_STORY)/VintagestoryServer.dll\" --tracelog --addModPath \"$(ProjectDir)/Mods\"$(VintageStoryDataPathArgs)",
|
||||||
|
"workingDirectory": "$(VINTAGE_STORY)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
675
RoamingBees/.gitignore
vendored
Normal file
675
RoamingBees/.gitignore
vendored
Normal file
@@ -0,0 +1,675 @@
|
|||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/csharp,visualstudio,visualstudiocode,rider
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=csharp,visualstudio,visualstudiocode,rider
|
||||||
|
|
||||||
|
### Csharp ###
|
||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.tlog
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||||
|
*.vbp
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||||
|
*.dsw
|
||||||
|
*.dsp
|
||||||
|
|
||||||
|
# Visual Studio 6 technical files
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# Visual Studio History (VSHistory) files
|
||||||
|
.vshistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
|
|
||||||
|
# VS Code files for those working on multiple tools
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Windows Installer files from build outputs
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
### Rider ###
|
||||||
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|
||||||
|
# User-specific stuff
|
||||||
|
.idea/**/workspace.xml
|
||||||
|
.idea/**/tasks.xml
|
||||||
|
.idea/**/usage.statistics.xml
|
||||||
|
.idea/**/dictionaries
|
||||||
|
.idea/**/shelf
|
||||||
|
|
||||||
|
# AWS User-specific
|
||||||
|
.idea/**/aws.xml
|
||||||
|
|
||||||
|
# Generated files
|
||||||
|
.idea/**/contentModel.xml
|
||||||
|
|
||||||
|
# Sensitive or high-churn files
|
||||||
|
.idea/**/dataSources/
|
||||||
|
.idea/**/dataSources.ids
|
||||||
|
.idea/**/dataSources.local.xml
|
||||||
|
.idea/**/sqlDataSources.xml
|
||||||
|
.idea/**/dynamic.xml
|
||||||
|
.idea/**/uiDesigner.xml
|
||||||
|
.idea/**/dbnavigator.xml
|
||||||
|
|
||||||
|
# Gradle
|
||||||
|
.idea/**/gradle.xml
|
||||||
|
.idea/**/libraries
|
||||||
|
|
||||||
|
# Gradle and Maven with auto-import
|
||||||
|
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||||
|
# since they will be recreated, and may cause churn. Uncomment if using
|
||||||
|
# auto-import.
|
||||||
|
# .idea/artifacts
|
||||||
|
# .idea/compiler.xml
|
||||||
|
# .idea/jarRepositories.xml
|
||||||
|
# .idea/modules.xml
|
||||||
|
# .idea/*.iml
|
||||||
|
# .idea/modules
|
||||||
|
# *.iml
|
||||||
|
# *.ipr
|
||||||
|
|
||||||
|
# CMake
|
||||||
|
cmake-build-*/
|
||||||
|
|
||||||
|
# Mongo Explorer plugin
|
||||||
|
.idea/**/mongoSettings.xml
|
||||||
|
|
||||||
|
# File-based project format
|
||||||
|
*.iws
|
||||||
|
|
||||||
|
# IntelliJ
|
||||||
|
out/
|
||||||
|
|
||||||
|
# mpeltonen/sbt-idea plugin
|
||||||
|
.idea_modules/
|
||||||
|
|
||||||
|
# JIRA plugin
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
# Cursive Clojure plugin
|
||||||
|
.idea/replstate.xml
|
||||||
|
|
||||||
|
# SonarLint plugin
|
||||||
|
.idea/sonarlint/
|
||||||
|
|
||||||
|
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||||
|
com_crashlytics_export_strings.xml
|
||||||
|
crashlytics.properties
|
||||||
|
crashlytics-build.properties
|
||||||
|
fabric.properties
|
||||||
|
|
||||||
|
# Editor-based Rest Client
|
||||||
|
.idea/httpRequests
|
||||||
|
|
||||||
|
# Android studio 3.1+ serialized cache file
|
||||||
|
.idea/caches/build_file_checksums.ser
|
||||||
|
|
||||||
|
### VisualStudioCode ###
|
||||||
|
!.vscode/*.code-snippets
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
|
||||||
|
### VisualStudioCode Patch ###
|
||||||
|
# Ignore all local history of files
|
||||||
|
.history
|
||||||
|
.ionide
|
||||||
|
|
||||||
|
### VisualStudio ###
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
|
||||||
|
# Others
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||||
|
|
||||||
|
# Visual Studio 6 technical files
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
|
||||||
|
# Visual Studio History (VSHistory) files
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
|
||||||
|
# VS Code files for those working on multiple tools
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
|
||||||
|
# Windows Installer files from build outputs
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
|
||||||
|
### VisualStudio Patch ###
|
||||||
|
# Additional files built by Visual Studio
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/csharp,visualstudio,visualstudiocode,rider
|
||||||
24
RoamingBees/RoamingBees/Behaviors/BlockBehaviorBeeSwarm.cs
Normal file
24
RoamingBees/RoamingBees/Behaviors/BlockBehaviorBeeSwarm.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace RoamingBees.Behaviors;
|
||||||
|
|
||||||
|
public class BlockBehaviorBeeSwarm(Block block) : BlockBehavior(block)
|
||||||
|
{
|
||||||
|
public override string GetPlacedBlockInfo(IWorldAccessor world, BlockPos pos, IPlayer forPlayer)
|
||||||
|
{
|
||||||
|
if (!world.EntityDebugMode)
|
||||||
|
return base.GetPlacedBlockInfo(world, pos, forPlayer);
|
||||||
|
|
||||||
|
var entityBehavior = block.GetBEBehavior<BlockEntityBehaviorBeeSwarm>(pos);
|
||||||
|
if (entityBehavior is null)
|
||||||
|
return base.GetPlacedBlockInfo(world, pos, forPlayer);
|
||||||
|
|
||||||
|
var str = $"Active bees: {entityBehavior.ActiveBeesCount} (hover: {entityBehavior.ActiveHoveringCount}, travel: {entityBehavior.ActiveTravelingCount}, scout: {entityBehavior.ActiveScoutingCount})\n";
|
||||||
|
str += $"Target bees: hover {entityBehavior.TargetHoveringCount}, travel {entityBehavior.TargetTravelingCount}, scout {entityBehavior.TargetScoutingCount}\n";
|
||||||
|
str += $"State: {entityBehavior.SwarmState}\n";
|
||||||
|
str += $"Time since last spawn: {entityBehavior.TimeSinceLastSpawn:F1} | hover: {entityBehavior.TimeSinceLastHoverSpawn:F1}\n";
|
||||||
|
|
||||||
|
return base.GetPlacedBlockInfo(world, pos, forPlayer) + str;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace RoamingBees.Behaviors;
|
||||||
|
|
||||||
|
public class BlockBehaviorRoamingBees(Block block) : BlockBehavior(block)
|
||||||
|
{
|
||||||
|
public override string GetPlacedBlockInfo(IWorldAccessor world, BlockPos pos, IPlayer forPlayer)
|
||||||
|
{
|
||||||
|
if (!world.EntityDebugMode)
|
||||||
|
return base.GetPlacedBlockInfo(world, pos, forPlayer);
|
||||||
|
|
||||||
|
var entityBehavior = block.GetBEBehavior<BlockEntityBehaviorRoamingBees>(pos);
|
||||||
|
if (entityBehavior is null)
|
||||||
|
return base.GetPlacedBlockInfo(world, pos, forPlayer);
|
||||||
|
|
||||||
|
var str = $"Roaming bees: {entityBehavior.ActiveBeesCount}\n";
|
||||||
|
str += $"Target roaming bees: {entityBehavior.TargetParticleCount}\n";
|
||||||
|
str += $"Time since last spawn + cooldown: {entityBehavior.TimeSinceLastSpawn:F1}\n";
|
||||||
|
|
||||||
|
str += $"Flowers around: {entityBehavior.FlowerCount}, crops around: {entityBehavior.CropCount}\n";
|
||||||
|
str += $"Scan progress: {entityBehavior.InitialScanProgress * 100f:F1}%, rescan progress: {entityBehavior.RescanProgress * 100f:F1}%\n";
|
||||||
|
|
||||||
|
return base.GetPlacedBlockInfo(world, pos, forPlayer) + str;
|
||||||
|
}
|
||||||
|
}
|
||||||
615
RoamingBees/RoamingBees/Behaviors/BlockEntityBehaviorBeeSwarm.cs
Normal file
615
RoamingBees/RoamingBees/Behaviors/BlockEntityBehaviorBeeSwarm.cs
Normal file
@@ -0,0 +1,615 @@
|
|||||||
|
using OrekiWoofsBees.Common;
|
||||||
|
using RoamingBees.Particles;
|
||||||
|
using RoamingBees.Particles.Catchup;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Vintagestory.API.Client;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.Datastructures;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace RoamingBees.Behaviors;
|
||||||
|
|
||||||
|
public class BlockEntityBehaviorBeeSwarm(BlockEntity blockEntity) : BlockEntityBehavior(blockEntity), IBeeSpawnHandler, IBeeSpawnCatchup
|
||||||
|
{
|
||||||
|
private const float spawn_cooldown_seconds = 2f;
|
||||||
|
private const float hover_spawn_cooldown_seconds = 0.1f;
|
||||||
|
private const int server_tick_frequency_decrease = 1;
|
||||||
|
private const bool swarm_collision_enabled = true;
|
||||||
|
|
||||||
|
private static readonly Random random = new();
|
||||||
|
|
||||||
|
private readonly List<InternalBeeParticle> activeBees = [];
|
||||||
|
private readonly List<BlockPos> candidateHivePositions = [];
|
||||||
|
|
||||||
|
private RoamingBeesModSystem? modSystem;
|
||||||
|
private bool initialized;
|
||||||
|
|
||||||
|
private BlockPos? originHivePos;
|
||||||
|
private BlockPos? targetHivePos;
|
||||||
|
|
||||||
|
private Vec3f[]? hoverSurfaceMap;
|
||||||
|
private Cuboidf[] shapeElements = [];
|
||||||
|
private Vector3 hiddenAnchor;
|
||||||
|
private string swarmSide = "north";
|
||||||
|
private double stateStartTotalHours;
|
||||||
|
private double buildingDurationHours = 3.0;
|
||||||
|
private double hangingDurationHours = 5.0;
|
||||||
|
private double migrationPhaseDurationHours = 5.0;
|
||||||
|
|
||||||
|
public int ActiveBeesCount => activeBees.Count;
|
||||||
|
public int ActiveHoveringCount => activeBees.Count(b => b.Role == BeeRole.Hovering);
|
||||||
|
public int ActiveTravelingCount => activeBees.Count(b => b.Role == BeeRole.Traveling);
|
||||||
|
public int ActiveScoutingCount => activeBees.Count(b => b.Role == BeeRole.Scouting);
|
||||||
|
public int TargetHoveringCount => ComputeHoverTarget(Config.Instance);
|
||||||
|
public int TargetTravelingCount => ComputeTravelingTarget(Config.Instance);
|
||||||
|
public int TargetScoutingCount => SwarmState == SwarmState.HangingOut ? Math.Max(0, Config.Instance.BeesPerSwarmTraveling) : 0;
|
||||||
|
|
||||||
|
public IEnumerable<BeeSpawnPacket> ActiveBeesPackets => activeBees.Select(x => x.SpawnPacket);
|
||||||
|
|
||||||
|
public float TimeSinceLastSpawn { get; private set; } = (float)(random.NextDouble() * spawn_cooldown_seconds);
|
||||||
|
public float TimeSinceLastHoverSpawn { get; private set; } = hover_spawn_cooldown_seconds;
|
||||||
|
public SwarmState SwarmState { get; private set; }
|
||||||
|
|
||||||
|
public override void Initialize(ICoreAPI api, JsonObject properties)
|
||||||
|
{
|
||||||
|
if (initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
base.Initialize(api, properties);
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
modSystem = api.ModLoader.GetModSystem<RoamingBeesModSystem>();
|
||||||
|
modSystem?.BeeSpawnPacketDistributor?.Register(Blockentity.Pos, this);
|
||||||
|
modSystem?.ClientChannel?.SendPacket(new BeeCatchupRequestPacket { HivePosition = Pos });
|
||||||
|
if (modSystem?.Mod.Info.Version.Contains("dev") == true && modSystem?.ClientChannel != null)
|
||||||
|
modSystem?.Mod.Logger.Event($"{nameof(BlockEntityBehaviorBeeSwarm)} sent {nameof(BeeCatchupRequestPacket)} Pos: {Pos}");
|
||||||
|
|
||||||
|
swarmSide = Blockentity.Block?.Variant?.ContainsKey("side") == true
|
||||||
|
? Blockentity.Block.Variant["side"]
|
||||||
|
: "north";
|
||||||
|
|
||||||
|
BuildHoverSurfaceMap();
|
||||||
|
|
||||||
|
var updateFrequency = 20;
|
||||||
|
if (api.Side == EnumAppSide.Server)
|
||||||
|
updateFrequency *= server_tick_frequency_decrease;
|
||||||
|
Blockentity.RegisterGameTickListener(OnTick, updateFrequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildHoverSurfaceMap()
|
||||||
|
{
|
||||||
|
shapeElements = ComputeShapeElements();
|
||||||
|
|
||||||
|
// find the union bounding rect of all elements' outward-facing surface
|
||||||
|
var uMin = new Vector3(float.MaxValue);
|
||||||
|
var uMax = new Vector3(float.MinValue);
|
||||||
|
foreach (var elem in shapeElements)
|
||||||
|
{
|
||||||
|
var eMin = elem.Start.ToVector3();
|
||||||
|
var eMax = elem.End.ToVector3();
|
||||||
|
uMin = Vector3.Min(uMin, eMin);
|
||||||
|
uMax = Vector3.Max(uMax, eMax);
|
||||||
|
}
|
||||||
|
|
||||||
|
var samples = new List<Vec3f>();
|
||||||
|
var sampleCount = 12;
|
||||||
|
for (var i = 0; i < sampleCount; i++)
|
||||||
|
{
|
||||||
|
float x, y, z;
|
||||||
|
switch (swarmSide)
|
||||||
|
{
|
||||||
|
case "north":
|
||||||
|
x = uMin.X + (float)random.NextDouble() * (uMax.X - uMin.X);
|
||||||
|
y = uMin.Y + (float)random.NextDouble() * (uMax.Y - uMin.Y);
|
||||||
|
z = uMax.Z;
|
||||||
|
break;
|
||||||
|
case "south":
|
||||||
|
x = uMin.X + (float)random.NextDouble() * (uMax.X - uMin.X);
|
||||||
|
y = uMin.Y + (float)random.NextDouble() * (uMax.Y - uMin.Y);
|
||||||
|
z = uMin.Z;
|
||||||
|
break;
|
||||||
|
case "east":
|
||||||
|
x = uMin.X;
|
||||||
|
y = uMin.Y + (float)random.NextDouble() * (uMax.Y - uMin.Y);
|
||||||
|
z = uMin.Z + (float)random.NextDouble() * (uMax.Z - uMin.Z);
|
||||||
|
break;
|
||||||
|
case "west":
|
||||||
|
x = uMax.X;
|
||||||
|
y = uMin.Y + (float)random.NextDouble() * (uMax.Y - uMin.Y);
|
||||||
|
z = uMin.Z + (float)random.NextDouble() * (uMax.Z - uMin.Z);
|
||||||
|
break;
|
||||||
|
case "up":
|
||||||
|
x = uMin.X + (float)random.NextDouble() * (uMax.X - uMin.X);
|
||||||
|
y = uMax.Y;
|
||||||
|
z = uMin.Z + (float)random.NextDouble() * (uMax.Z - uMin.Z);
|
||||||
|
break;
|
||||||
|
default: // down
|
||||||
|
x = uMin.X + (float)random.NextDouble() * (uMax.X - uMin.X);
|
||||||
|
y = uMin.Y;
|
||||||
|
z = uMin.Z + (float)random.NextDouble() * (uMax.Z - uMin.Z);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
samples.Add(new Vec3f(x, y, z));
|
||||||
|
}
|
||||||
|
|
||||||
|
hoverSurfaceMap = [.. samples];
|
||||||
|
hiddenAnchor = SwarmBeePathGeneration.ComputeHiddenAnchor(hoverSurfaceMap, swarmSide);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cuboidf[] ComputeShapeElements()
|
||||||
|
{
|
||||||
|
var block = Blockentity.Block;
|
||||||
|
if (block?.Shape?.Base is null)
|
||||||
|
return [new Cuboidf(0.25f, 0.25f, 0.25f, 0.75f, 0.75f, 0.75f)];
|
||||||
|
|
||||||
|
var shapeLocation = block.Shape.Base.Clone();
|
||||||
|
shapeLocation.Path = "shapes/" + shapeLocation.Path + ".json";
|
||||||
|
var shapeAsset = Api.Assets.TryGet(shapeLocation);
|
||||||
|
if (shapeAsset is null)
|
||||||
|
return [new Cuboidf(0.25f, 0.25f, 0.25f, 0.75f, 0.75f, 0.75f)];
|
||||||
|
|
||||||
|
var shape = shapeAsset.ToObject<Shape>();
|
||||||
|
if (shape?.Elements is null || shape.Elements.Length == 0)
|
||||||
|
return [new Cuboidf(0.25f, 0.25f, 0.25f, 0.75f, 0.75f, 0.75f)];
|
||||||
|
|
||||||
|
var rotX = block.Shape.rotateX;
|
||||||
|
var rotY = block.Shape.rotateY;
|
||||||
|
var rotZ = block.Shape.rotateZ;
|
||||||
|
|
||||||
|
var cuboids = new List<Cuboidf>();
|
||||||
|
CollectElementCuboids(shape.Elements, cuboids, rotX, rotY, rotZ);
|
||||||
|
return cuboids.Count > 0 ? [.. cuboids] : [new Cuboidf(0.25f, 0.25f, 0.25f, 0.75f, 0.75f, 0.75f)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CollectElementCuboids(ShapeElement[] elements, List<Cuboidf> cuboids, float rotX, float rotY, float rotZ)
|
||||||
|
{
|
||||||
|
foreach (var element in elements)
|
||||||
|
{
|
||||||
|
if (element.From is not null && element.To is not null)
|
||||||
|
{
|
||||||
|
var x1 = MathF.Min((float)element.From[0], (float)element.To[0]) / 16f;
|
||||||
|
var y1 = MathF.Min((float)element.From[1], (float)element.To[1]) / 16f;
|
||||||
|
var z1 = MathF.Min((float)element.From[2], (float)element.To[2]) / 16f;
|
||||||
|
var x2 = MathF.Max((float)element.From[0], (float)element.To[0]) / 16f;
|
||||||
|
var y2 = MathF.Max((float)element.From[1], (float)element.To[1]) / 16f;
|
||||||
|
var z2 = MathF.Max((float)element.From[2], (float)element.To[2]) / 16f;
|
||||||
|
|
||||||
|
if (rotX != 0 || rotY != 0 || rotZ != 0)
|
||||||
|
RotateBounds(ref x1, ref y1, ref z1, ref x2, ref y2, ref z2, rotX, rotY, rotZ);
|
||||||
|
|
||||||
|
cuboids.Add(new Cuboidf(
|
||||||
|
Math.Clamp(x1, 0f, 1f), Math.Clamp(y1, 0f, 1f), Math.Clamp(z1, 0f, 1f),
|
||||||
|
Math.Clamp(x2, 0f, 1f), Math.Clamp(y2, 0f, 1f), Math.Clamp(z2, 0f, 1f)));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.Children is not null)
|
||||||
|
CollectElementCuboids(element.Children, cuboids, rotX, rotY, rotZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RotateBounds(ref float minX, ref float minY, ref float minZ, ref float maxX, ref float maxY, ref float maxZ, float rotXDeg, float rotYDeg, float rotZDeg)
|
||||||
|
{
|
||||||
|
// rotate all 8 corners of the AABB around (0.5, 0.5, 0.5) center and recompute bounds
|
||||||
|
var corners = new Vector3[8]
|
||||||
|
{
|
||||||
|
new(minX, minY, minZ), new(maxX, minY, minZ),
|
||||||
|
new(minX, maxY, minZ), new(maxX, maxY, minZ),
|
||||||
|
new(minX, minY, maxZ), new(maxX, minY, maxZ),
|
||||||
|
new(minX, maxY, maxZ), new(maxX, maxY, maxZ),
|
||||||
|
};
|
||||||
|
|
||||||
|
var center = new Vector3(0.5f, 0.5f, 0.5f);
|
||||||
|
var rotation = Matrix4x4.CreateFromYawPitchRoll(
|
||||||
|
rotYDeg * MathF.PI / 180f,
|
||||||
|
rotXDeg * MathF.PI / 180f,
|
||||||
|
rotZDeg * MathF.PI / 180f);
|
||||||
|
|
||||||
|
var newMin = new Vector3(float.MaxValue);
|
||||||
|
var newMax = new Vector3(float.MinValue);
|
||||||
|
|
||||||
|
foreach (var corner in corners)
|
||||||
|
{
|
||||||
|
var rotated = Vector3.Transform(corner - center, rotation) + center;
|
||||||
|
newMin = Vector3.Min(newMin, rotated);
|
||||||
|
newMax = Vector3.Max(newMax, rotated);
|
||||||
|
}
|
||||||
|
|
||||||
|
minX = newMin.X; minY = newMin.Y; minZ = newMin.Z;
|
||||||
|
maxX = newMax.X; maxY = newMax.Y; maxZ = newMax.Z;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBlockRemoved()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
base.OnBlockRemoved();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBlockUnloaded()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
base.OnBlockUnloaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
if (modSystem is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
modSystem.GlobalActiveBees -= activeBees.Count;
|
||||||
|
modSystem.BeeSpawnPacketDistributor?.Unregister(Blockentity.Pos);
|
||||||
|
activeBees.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly TreeAttribute swarmAttributeSnapshot = new();
|
||||||
|
|
||||||
|
private void ReadSwarmAttributesFromBlockEntity()
|
||||||
|
{
|
||||||
|
swarmAttributeSnapshot.Clear();
|
||||||
|
Blockentity.ToTreeAttributes(swarmAttributeSnapshot);
|
||||||
|
|
||||||
|
SwarmState = (SwarmState)swarmAttributeSnapshot.GetInt("roamingbees_swarm_state");
|
||||||
|
originHivePos = swarmAttributeSnapshot.GetBlockPos("roamingbees_swarm_originHivePos");
|
||||||
|
targetHivePos = swarmAttributeSnapshot.GetBlockPos("roamingbees_swarm_targetHivePos");
|
||||||
|
stateStartTotalHours = swarmAttributeSnapshot.GetDouble("stateStartTotalHours", stateStartTotalHours);
|
||||||
|
buildingDurationHours = swarmAttributeSnapshot.GetDouble("buildingDurationHours", buildingDurationHours);
|
||||||
|
hangingDurationHours = swarmAttributeSnapshot.GetDouble("hangingDurationHours", hangingDurationHours);
|
||||||
|
migrationPhaseDurationHours = swarmAttributeSnapshot.GetDouble("MigrationPhaseDurationHours", migrationPhaseDurationHours);
|
||||||
|
|
||||||
|
candidateHivePositions.Clear();
|
||||||
|
if (swarmAttributeSnapshot["roamingbees_swarm_candidateHives"] is not TreeArrayAttribute candidates)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var entry in candidates.value.OfType<ITreeAttribute>())
|
||||||
|
{
|
||||||
|
var pos = entry?.GetBlockPos("pos");
|
||||||
|
if (pos is not null)
|
||||||
|
candidateHivePositions.Add(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleBeeParticleSpawn(BeeSpawnPacket packet, bool catchup = false)
|
||||||
|
{
|
||||||
|
if (Blockentity.Pos != packet.HivePosition)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (packet.Path is null || packet.Path.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var points = packet.Path.Select(x => x.ToPoint()).ToArray();
|
||||||
|
var bee = new InternalBeeParticle(packet.EntrancePosition, GetFrontDirection(), points, packet.Role, packet.DespawnPosition, packet);
|
||||||
|
if (packet.Role == BeeRole.Hovering)
|
||||||
|
bee.LookDirection = -GetFrontDirection();
|
||||||
|
activeBees.Add(bee);
|
||||||
|
if (catchup)
|
||||||
|
{
|
||||||
|
var totalDelta = Api.World.Calendar.ElapsedSeconds - packet.TimeElapsedSeconds;
|
||||||
|
if (modSystem?.Mod.Info.Version.Contains("dev") == true)
|
||||||
|
modSystem.Mod.Logger.Notification($"HandleBeeParticleSpawn catchup totalDelta: {totalDelta}s");
|
||||||
|
for (var i = 0; i < totalDelta / 0.1f; i++)
|
||||||
|
bee.Step(0.1f, 0.1f); // todo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTick(float dt)
|
||||||
|
{
|
||||||
|
var stopwatch = Stopwatch.StartNew();
|
||||||
|
Update(dt);
|
||||||
|
stopwatch.Stop();
|
||||||
|
if (stopwatch.Elapsed.TotalSeconds > 0.2)
|
||||||
|
modSystem?.Mod?.Logger.Warning($"{nameof(BlockEntityBehaviorBeeSwarm)} {nameof(OnTick)} took {stopwatch.Elapsed.TotalSeconds:F2}s");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update(float dt)
|
||||||
|
{
|
||||||
|
if (dt > 10f)
|
||||||
|
return;
|
||||||
|
dt = Math.Min(0.5f, dt);
|
||||||
|
|
||||||
|
ReadSwarmAttributesFromBlockEntity();
|
||||||
|
|
||||||
|
var windVec = Api.World.BlockAccessor.GetWindSpeedAt(Blockentity.Pos);
|
||||||
|
var windSpeed = (float)Math.Min(windVec.Length(), 1.0);
|
||||||
|
|
||||||
|
for (int i = activeBees.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var bee = activeBees[i];
|
||||||
|
|
||||||
|
if (Api.Side == EnumAppSide.Client)
|
||||||
|
HandleBeeClientSide(dt, windSpeed, bee);
|
||||||
|
|
||||||
|
if (Api.Side == EnumAppSide.Server)
|
||||||
|
{
|
||||||
|
for (var j = 0; j < server_tick_frequency_decrease; j++)
|
||||||
|
bee.Step(dt / server_tick_frequency_decrease, windSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bee.ShouldBeDespawned && modSystem != null)
|
||||||
|
{
|
||||||
|
activeBees.RemoveAt(i);
|
||||||
|
modSystem.GlobalActiveBees--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeSinceLastSpawn += dt;
|
||||||
|
TimeSinceLastHoverSpawn += dt;
|
||||||
|
if (Api.Side == EnumAppSide.Server)
|
||||||
|
{
|
||||||
|
TrySpawnSwarmBee();
|
||||||
|
TrySpawnHoverBee();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleBeeClientSide(float dt, float windSpeed, InternalBeeParticle bee)
|
||||||
|
{
|
||||||
|
bee.Step(dt, windSpeed);
|
||||||
|
|
||||||
|
// Collision response: push the bee outward if it has drifted inside any
|
||||||
|
// of the swarm's shape element cuboids, unless heading to a despawn/slowdown point.
|
||||||
|
if (swarm_collision_enabled && (bee.Target is null || !bee.Target.Value.IsSlowdown))
|
||||||
|
{
|
||||||
|
var p = bee.Position;
|
||||||
|
var outDir = swarmSide switch
|
||||||
|
{
|
||||||
|
"north" => new Vector3(0f, 0f, 1f),
|
||||||
|
"south" => new Vector3(0f, 0f, -1f),
|
||||||
|
"east" => new Vector3(-1f, 0f, 0f),
|
||||||
|
"west" => new Vector3(1f, 0f, 0f),
|
||||||
|
"up" => new Vector3(0f, 1f, 0f),
|
||||||
|
"down" => new Vector3(0f, -1f, 0f),
|
||||||
|
_ => new Vector3(0f, 0f, 1f),
|
||||||
|
};
|
||||||
|
foreach (var elem in shapeElements)
|
||||||
|
{
|
||||||
|
var eMin = elem.Start.ToVector3();
|
||||||
|
var eMax = elem.End.ToVector3();
|
||||||
|
if (p.X >= eMin.X && p.X <= eMax.X &&
|
||||||
|
p.Y >= eMin.Y && p.Y <= eMax.Y &&
|
||||||
|
p.Z >= eMin.Z && p.Z <= eMax.Z)
|
||||||
|
{
|
||||||
|
bee.SolveCollision(eMin, eMax, outDir);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Config.Instance.ReceiveParticles && Api.World is IClientWorldAccessor clientWorld)
|
||||||
|
BeeVisualParticleRenderer.Spawn(clientWorld, Blockentity.Pos, bee);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TrySpawnSwarmBee()
|
||||||
|
{
|
||||||
|
if (TimeSinceLastSpawn < spawn_cooldown_seconds)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (modSystem?.ServerChannel is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
if (modSystem.GlobalActiveBees >= cfg.MaxGlobalRoamingBees)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var (path, role, startingPosition, despawnPosition) = GeneratePathForCurrentState();
|
||||||
|
|
||||||
|
if (path is null || path.Length == 0)
|
||||||
|
{
|
||||||
|
TimeSinceLastSpawn = 0f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var message = new BeeSpawnPacket
|
||||||
|
{
|
||||||
|
HivePosition = Blockentity.Pos,
|
||||||
|
Path = [.. path.Select(x => BeePlannedPathPointContract.FromPoint(x))],
|
||||||
|
Facing = swarmSide,
|
||||||
|
Role = role,
|
||||||
|
EntrancePosition = startingPosition,
|
||||||
|
DespawnPosition = despawnPosition,
|
||||||
|
TimeElapsedSeconds = Api.World.Calendar.ElapsedSeconds,
|
||||||
|
};
|
||||||
|
|
||||||
|
activeBees.Add(new InternalBeeParticle(startingPosition, GetFrontDirection(), path, role, despawnPosition, message));
|
||||||
|
modSystem.GlobalActiveBees++;
|
||||||
|
TimeSinceLastSpawn = 0f;
|
||||||
|
modSystem.ServerChannel.BroadcastPacket(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TrySpawnHoverBee()
|
||||||
|
{
|
||||||
|
if (TimeSinceLastHoverSpawn < hover_spawn_cooldown_seconds)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (modSystem?.ServerChannel is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
if (modSystem.GlobalActiveBees >= cfg.MaxGlobalRoamingBees)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (hoverSurfaceMap is null || hoverSurfaceMap.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ActiveHoveringCount >= ComputeHoverTarget(cfg))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var path = SwarmBeePathGeneration.GenerateHoverPath(Api.World.BlockAccessor, Blockentity.Pos, hoverSurfaceMap, hiddenAnchor, swarmSide);
|
||||||
|
if (path is null || path.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var message = new BeeSpawnPacket
|
||||||
|
{
|
||||||
|
HivePosition = Blockentity.Pos,
|
||||||
|
Path = [.. path.Select(x => BeePlannedPathPointContract.FromPoint(x))],
|
||||||
|
Facing = swarmSide,
|
||||||
|
Role = BeeRole.Hovering,
|
||||||
|
EntrancePosition = hiddenAnchor,
|
||||||
|
DespawnPosition = hiddenAnchor,
|
||||||
|
TimeElapsedSeconds = Api.World.Calendar.ElapsedSeconds,
|
||||||
|
};
|
||||||
|
|
||||||
|
var hoverBee = new InternalBeeParticle(hiddenAnchor, GetFrontDirection(), path, BeeRole.Hovering, hiddenAnchor, message);
|
||||||
|
hoverBee.LookDirection = -GetFrontDirection();
|
||||||
|
activeBees.Add(hoverBee);
|
||||||
|
modSystem.GlobalActiveBees++;
|
||||||
|
TimeSinceLastHoverSpawn = 0f;
|
||||||
|
modSystem.ServerChannel.BroadcastPacket(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ComputeHoverTarget(Config cfg)
|
||||||
|
{
|
||||||
|
var maxHover = Math.Max(0, cfg.BeesPerSwarmHovering);
|
||||||
|
return SwarmState switch
|
||||||
|
{
|
||||||
|
SwarmState.BuildingSwarm => ComputePhaseScaledTarget(maxHover, GetPhaseProgress(buildingDurationHours), invert: false),
|
||||||
|
SwarmState.HangingOut => maxHover,
|
||||||
|
SwarmState.MigratingToNewHive => ComputePhaseScaledTarget(maxHover, GetPhaseProgress(migrationPhaseDurationHours), invert: true),
|
||||||
|
_ => maxHover,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private int ComputeTravelingTarget(Config cfg)
|
||||||
|
{
|
||||||
|
var maxTravel = Math.Max(0, cfg.BeesPerSwarmTraveling);
|
||||||
|
return SwarmState switch
|
||||||
|
{
|
||||||
|
SwarmState.MigratingToNewHive => GetPhaseProgress(migrationPhaseDurationHours) >= 0.9d ? 0 : maxTravel,
|
||||||
|
_ => maxTravel,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private double GetPhaseProgress(double durationHours)
|
||||||
|
{
|
||||||
|
if (Api?.World?.Calendar is null || durationHours <= 0.0001)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return Math.Clamp((Api.World.Calendar.TotalHours - stateStartTotalHours) / durationHours, 0d, 1d);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ComputePhaseScaledTarget(int maxTarget, double progress, bool invert)
|
||||||
|
{
|
||||||
|
var scaledProgress = Math.Clamp(progress / 0.9d, 0d, 1d);
|
||||||
|
var ratio = invert ? 1d - scaledProgress : scaledProgress;
|
||||||
|
return (int)Math.Round(maxTarget * ratio, MidpointRounding.AwayFromZero);
|
||||||
|
}
|
||||||
|
|
||||||
|
private (BeePlannedPathPoint[]? path, BeeRole role, Vector3 startingPosition, Vector3 despawnPosition) GeneratePathForCurrentState()
|
||||||
|
{
|
||||||
|
if (hoverSurfaceMap is null || hoverSurfaceMap.Length == 0)
|
||||||
|
return (null, BeeRole.Hovering, hiddenAnchor, hiddenAnchor);
|
||||||
|
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
var blockAccessor = Api.World.BlockAccessor;
|
||||||
|
|
||||||
|
switch (SwarmState)
|
||||||
|
{
|
||||||
|
case SwarmState.BuildingSwarm:
|
||||||
|
{
|
||||||
|
var path = GenerateBuildingSwarmPath(blockAccessor, cfg);
|
||||||
|
var start = path is not null ? path[0].Position : hiddenAnchor;
|
||||||
|
return (path, BeeRole.Traveling, start, hiddenAnchor);
|
||||||
|
}
|
||||||
|
case SwarmState.HangingOut:
|
||||||
|
return (GenerateHangingOutPath(blockAccessor, cfg), BeeRole.Scouting, hiddenAnchor, hiddenAnchor);
|
||||||
|
case SwarmState.MigratingToNewHive:
|
||||||
|
{
|
||||||
|
var (path, despawn) = GenerateMigrationStatePath(blockAccessor, cfg);
|
||||||
|
return (path, BeeRole.Traveling, hiddenAnchor, despawn);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return (null, BeeRole.Hovering, hiddenAnchor, hiddenAnchor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeePlannedPathPoint[]? GenerateBuildingSwarmPath(IBlockAccessor blockAccessor, Config cfg)
|
||||||
|
{
|
||||||
|
var targetTraveling = ComputeTravelingTarget(cfg);
|
||||||
|
if (originHivePos is null || targetTraveling <= 0 || ActiveTravelingCount >= targetTraveling)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var originBehavior = GetRoamingBehavior(originHivePos);
|
||||||
|
if (originBehavior is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var originEntrance = ToSwarmRelative(originHivePos) + originBehavior.GetEntrancePosition();
|
||||||
|
var originFront = originBehavior.GetFrontDirection();
|
||||||
|
return SwarmBeePathGeneration.GenerateBuildingPath(blockAccessor, Blockentity.Pos, originEntrance, originFront, hiddenAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BeePlannedPathPoint[]? GenerateHangingOutPath(IBlockAccessor blockAccessor, Config cfg)
|
||||||
|
{
|
||||||
|
if (candidateHivePositions.Count == 0 || ActiveScoutingCount >= cfg.BeesPerSwarmTraveling || random.NextDouble() >= 0.3)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var candidate = candidateHivePositions[random.Next(candidateHivePositions.Count)];
|
||||||
|
var candidateBehavior = GetRoamingBehavior(candidate);
|
||||||
|
if (candidateBehavior is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var targetEntrance = ToSwarmRelative(candidate) + candidateBehavior.GetEntrancePosition();
|
||||||
|
var targetFront = candidateBehavior.GetFrontDirection();
|
||||||
|
return SwarmBeePathGeneration.GenerateScoutPath(blockAccessor, Blockentity.Pos, targetEntrance, targetFront, hiddenAnchor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private (BeePlannedPathPoint[]? path, Vector3 despawnPosition) GenerateMigrationStatePath(IBlockAccessor blockAccessor, Config cfg)
|
||||||
|
{
|
||||||
|
var targetTraveling = ComputeTravelingTarget(cfg);
|
||||||
|
if (targetHivePos is null || targetTraveling <= 0 || ActiveTravelingCount >= targetTraveling)
|
||||||
|
return (null, hiddenAnchor);
|
||||||
|
|
||||||
|
var targetBehavior = GetRoamingBehavior(targetHivePos);
|
||||||
|
if (targetBehavior is null)
|
||||||
|
return (null, hiddenAnchor);
|
||||||
|
|
||||||
|
var targetEntrance = ToSwarmRelative(targetHivePos) + targetBehavior.GetEntrancePosition();
|
||||||
|
var targetFront = targetBehavior.GetFrontDirection();
|
||||||
|
return (SwarmBeePathGeneration.GenerateMigrationPath(blockAccessor, Blockentity.Pos, targetEntrance, targetFront, hiddenAnchor), targetEntrance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BlockEntityBehaviorRoamingBees? GetRoamingBehavior(BlockPos pos)
|
||||||
|
{
|
||||||
|
var be = Api.World.BlockAccessor.GetBlockEntity(pos);
|
||||||
|
return be?.GetBehavior<BlockEntityBehaviorRoamingBees>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3 ToSwarmRelative(BlockPos worldPos)
|
||||||
|
{
|
||||||
|
return new Vector3(
|
||||||
|
worldPos.X - Blockentity.Pos.X,
|
||||||
|
worldPos.Y - Blockentity.Pos.Y,
|
||||||
|
worldPos.Z - Blockentity.Pos.Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3 GetFrontDirection()
|
||||||
|
{
|
||||||
|
return swarmSide switch
|
||||||
|
{
|
||||||
|
"north" => new Vector3(0f, 0f, 1f),
|
||||||
|
"east" => new Vector3(-1f, 0f, 0f),
|
||||||
|
"south" => new Vector3(0f, 0f, -1f),
|
||||||
|
"west" => new Vector3(1f, 0f, 0f),
|
||||||
|
"down" => new Vector3(0f, -1f, 0f),
|
||||||
|
"up" => new Vector3(0f, 1f, 0f),
|
||||||
|
_ => new Vector3(0f, 0f, 1f),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ToTreeAttributes(ITreeAttribute tree)
|
||||||
|
{
|
||||||
|
base.ToTreeAttributes(tree);
|
||||||
|
tree.SetFloat("dbg_timeSinceLastSpawn", TimeSinceLastSpawn);
|
||||||
|
tree.SetFloat("dbg_timeSinceLastHoverSpawn", TimeSinceLastHoverSpawn);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FromTreeAttributes(ITreeAttribute tree, IWorldAccessor worldAccessForResolve)
|
||||||
|
{
|
||||||
|
base.FromTreeAttributes(tree, worldAccessForResolve);
|
||||||
|
if (Api?.Side.IsClient() == true)
|
||||||
|
{
|
||||||
|
TimeSinceLastSpawn = tree.GetFloat("dbg_timeSinceLastSpawn", TimeSinceLastSpawn);
|
||||||
|
TimeSinceLastHoverSpawn = tree.GetFloat("dbg_timeSinceLastHoverSpawn", TimeSinceLastHoverSpawn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,433 @@
|
|||||||
|
using OrekiWoofsBees.Common;
|
||||||
|
using RoamingBees.Particles;
|
||||||
|
using RoamingBees.Particles.Catchup;
|
||||||
|
using RoamingBees.Utilities;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Vintagestory.API.Client;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.Datastructures;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
using Vintagestory.GameContent;
|
||||||
|
|
||||||
|
namespace RoamingBees.Behaviors;
|
||||||
|
|
||||||
|
public class BlockEntityBehaviorRoamingBees(BlockEntity blockEntity) : BlockEntityBehavior(blockEntity), IBeeSpawnHandler, IBeeSpawnCatchup
|
||||||
|
{
|
||||||
|
private const float spawn_cooldown_seconds = 1.5f;
|
||||||
|
private const string flower_count_attribute = "roamingbees_flowerCount";
|
||||||
|
private const string crop_count_attribute = "roamingbees_cropCount";
|
||||||
|
private const string initial_scan_progress_attribute = "roamingbees_initialScanProgress";
|
||||||
|
private const string rescan_progress_attribute = "roamingbees_rescanProgress";
|
||||||
|
|
||||||
|
private static readonly Random random = new();
|
||||||
|
|
||||||
|
private readonly List<InternalBeeParticle> activeBees = [];
|
||||||
|
private readonly TreeAttribute entityAttributeSnapshot = new();
|
||||||
|
|
||||||
|
private string facingVariantKey = "side";
|
||||||
|
private Dictionary<string, Vector3>? entrancePositions;
|
||||||
|
private Dictionary<string, Vector3>? frontDirections;
|
||||||
|
private RoamingBeesModSystem? modSystem;
|
||||||
|
private IPlantPositionRegistry? plantPositionRegistry;
|
||||||
|
private float rainfall;
|
||||||
|
private float temperature;
|
||||||
|
private bool initialized;
|
||||||
|
public const string TARGET_BEE_PARTICLE_COUNT_ATTRIBUTE = "roamingbees_targetBeeParticleCount";
|
||||||
|
public const string RADIUS_ATTRIBUTE = "roamingbees_radius";
|
||||||
|
public const string NETWORK_CHANNEL_NAME = "bee particle spawns";
|
||||||
|
public const int SERVER_TICK_FREQUENCY_DECREASE = 50;
|
||||||
|
|
||||||
|
public int ActiveBeesCount => activeBees.Count;
|
||||||
|
public IEnumerable<BeeSpawnPacket> ActiveBeesPackets => activeBees.Select(x => x.SpawnPacket);
|
||||||
|
public float TimeSinceLastSpawn { get; private set; } = (float)(random.NextDouble() * spawn_cooldown_seconds);
|
||||||
|
public int TargetParticleCount { get; private set; }
|
||||||
|
public int ScanRadius => GetRadiusFromBlockEntity();
|
||||||
|
public int FlowerCount { get; private set; }
|
||||||
|
public int CropCount { get; private set; }
|
||||||
|
public float InitialScanProgress { get; private set; }
|
||||||
|
public float RescanProgress { get; private set; }
|
||||||
|
|
||||||
|
|
||||||
|
public override void Initialize(ICoreAPI api, JsonObject properties)
|
||||||
|
{
|
||||||
|
if (initialized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
base.Initialize(api, properties);
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
facingVariantKey = properties["facingVariantKey"].AsString("side");
|
||||||
|
entrancePositions = VectorParsing.ParseVector3Map(properties["entrancePositions"]);
|
||||||
|
frontDirections = VectorParsing.ParseVector3Map(properties["frontDirections"]);
|
||||||
|
|
||||||
|
modSystem = api.ModLoader.GetModSystem<RoamingBeesModSystem>();
|
||||||
|
modSystem?.BeeSpawnPacketDistributor?.Register(Blockentity.Pos, this);
|
||||||
|
modSystem?.ClientChannel?.SendPacket(new BeeCatchupRequestPacket { HivePosition = Pos });
|
||||||
|
if (modSystem?.Mod.Info.Version.Contains("dev") == true && modSystem?.ClientChannel != null)
|
||||||
|
modSystem?.Mod.Logger.Event($"{nameof(BlockEntityBehaviorRoamingBees)} sent {nameof(BeeCatchupRequestPacket)} Pos: {Pos}");
|
||||||
|
|
||||||
|
plantPositionRegistry = api.GetPlantPositionRegistry();
|
||||||
|
var radius = GetRadiusFromBlockEntity();
|
||||||
|
plantPositionRegistry?.RegisterBeehive(Blockentity.Pos, radius);
|
||||||
|
|
||||||
|
var updateFrequency = 20;
|
||||||
|
if (api.Side == EnumAppSide.Server)
|
||||||
|
updateFrequency *= SERVER_TICK_FREQUENCY_DECREASE;
|
||||||
|
Blockentity.RegisterGameTickListener(OnParticleTick, updateFrequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetRadiusFromBlockEntity()
|
||||||
|
{
|
||||||
|
var radius = properties[RADIUS_ATTRIBUTE].AsInt(10);
|
||||||
|
|
||||||
|
var tree = new TreeAttribute();
|
||||||
|
Blockentity.ToTreeAttributes(tree);
|
||||||
|
radius = tree.GetAsInt(RADIUS_ATTRIBUTE, radius);
|
||||||
|
return radius;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetTargetBeeParticleCountFromBlockEntity()
|
||||||
|
{
|
||||||
|
var targetBeeParticleCount = properties[TARGET_BEE_PARTICLE_COUNT_ATTRIBUTE].AsInt(10);
|
||||||
|
targetBeeParticleCount = entityAttributeSnapshot.GetInt(TARGET_BEE_PARTICLE_COUNT_ATTRIBUTE, targetBeeParticleCount);
|
||||||
|
return targetBeeParticleCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBlockRemoved()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
base.OnBlockRemoved();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnBlockUnloaded()
|
||||||
|
{
|
||||||
|
Clear();
|
||||||
|
base.OnBlockUnloaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void ToTreeAttributes(ITreeAttribute tree)
|
||||||
|
{
|
||||||
|
base.ToTreeAttributes(tree);
|
||||||
|
|
||||||
|
tree.SetInt(flower_count_attribute, FlowerCount);
|
||||||
|
tree.SetInt(crop_count_attribute, CropCount);
|
||||||
|
tree.SetFloat(initial_scan_progress_attribute, InitialScanProgress);
|
||||||
|
tree.SetFloat(rescan_progress_attribute, RescanProgress);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void FromTreeAttributes(ITreeAttribute tree, IWorldAccessor worldAccessForResolve)
|
||||||
|
{
|
||||||
|
base.FromTreeAttributes(tree, worldAccessForResolve);
|
||||||
|
|
||||||
|
if (Api is null || Api.Side == EnumAppSide.Server)
|
||||||
|
return;
|
||||||
|
FlowerCount = tree.GetInt(flower_count_attribute);
|
||||||
|
CropCount = tree.GetInt(crop_count_attribute);
|
||||||
|
InitialScanProgress = tree.GetFloat(initial_scan_progress_attribute);
|
||||||
|
RescanProgress = tree.GetFloat(rescan_progress_attribute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleBeeParticleSpawn(BeeSpawnPacket packet, bool catchup = false)
|
||||||
|
{
|
||||||
|
if (Blockentity.Pos != packet.HivePosition)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (packet.Path is null)
|
||||||
|
{
|
||||||
|
modSystem?.Mod.Logger.Warning("Received packet with Path == null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet.Path.Length == 0)
|
||||||
|
{
|
||||||
|
modSystem?.Mod.Logger.Warning("Received packet with Path.Length == 0");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var facing = packet.Facing ?? GetFacing();
|
||||||
|
Vector3 entrancePosition = GetEntrancePosition(facing);
|
||||||
|
var bee = new InternalBeeParticle(entrancePosition, GetFrontDirection(facing), [.. packet.Path.Select(x => x.ToPoint())], BeeRole.Forager, entrancePosition, packet);
|
||||||
|
activeBees.Add(bee);
|
||||||
|
if (catchup)
|
||||||
|
{
|
||||||
|
var totalDelta = Api.World.Calendar.ElapsedSeconds - packet.TimeElapsedSeconds;
|
||||||
|
if (modSystem?.Mod.Info.Version.Contains("dev") == true)
|
||||||
|
modSystem.Mod.Logger.Notification($"HandleBeeParticleSpawn catchup totalDelta: {totalDelta}s");
|
||||||
|
for (var i = 0; i < totalDelta / 0.1f; i++)
|
||||||
|
bee.Step(0.1f, 0.1f); // todo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
if (modSystem is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
modSystem.GlobalActiveBees -= activeBees.Count;
|
||||||
|
modSystem.BeeSpawnPacketDistributor?.Unregister(Blockentity.Pos);
|
||||||
|
plantPositionRegistry?.UnregisterBeehive(Blockentity.Pos);
|
||||||
|
|
||||||
|
activeBees.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnParticleTick(float dt)
|
||||||
|
{
|
||||||
|
if (Block is BlockSkep && !Config.Instance.EnableOnVanillaSkeps)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var isFgc = Block?.Code?.Domain == "fromgoldencombs";
|
||||||
|
var path = Block?.Code?.Path ?? string.Empty;
|
||||||
|
var isFgcLangstroth = isFgc && path.Contains("langstrothstack", StringComparison.OrdinalIgnoreCase);
|
||||||
|
var isFgcCeramic = isFgc && path.Contains("ceramicbroodpot", StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
|
if (isFgcLangstroth)
|
||||||
|
{
|
||||||
|
if (!Config.Instance.EnableOnFgcLangstroth)
|
||||||
|
return;
|
||||||
|
if (!HasRequiredLangstrothBase())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isFgcCeramic && !Config.Instance.EnableOnFgcCeramic)
|
||||||
|
return;
|
||||||
|
|
||||||
|
entityAttributeSnapshot.Clear();
|
||||||
|
Blockentity.ToTreeAttributes(entityAttributeSnapshot);
|
||||||
|
|
||||||
|
var targetBeeParticleCount = GetTargetBeeParticleCountFromBlockEntity();
|
||||||
|
if (targetBeeParticleCount <= 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TargetParticleCount = Math.Max(0, targetBeeParticleCount);
|
||||||
|
if (Block is BlockSkep)
|
||||||
|
TargetParticleCount = Config.Instance.RoamingBeesPerVanillaSkep;
|
||||||
|
if (isFgcLangstroth)
|
||||||
|
TargetParticleCount = Config.Instance.RoamingBeesPerFgcLangstroth;
|
||||||
|
if (isFgcCeramic)
|
||||||
|
TargetParticleCount = Config.Instance.RoamingBeesPerFgcCeramic;
|
||||||
|
var nearbyPlantPositions = Api.Side == EnumAppSide.Server
|
||||||
|
? GetNearbyPlantPositions()
|
||||||
|
: [];
|
||||||
|
|
||||||
|
Update(dt, nearbyPlantPositions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Update(float dt, IEnumerable<Vector3> nearbyPlantPositions)
|
||||||
|
{
|
||||||
|
if (dt > 10f)
|
||||||
|
return;
|
||||||
|
dt = Math.Min(0.5f, dt);
|
||||||
|
|
||||||
|
var windVec = Api.World.BlockAccessor.GetWindSpeedAt(Blockentity.Pos);
|
||||||
|
var windSpeed = (float)Math.Min(windVec.Length(), 1.0);
|
||||||
|
var climate = Api.World.BlockAccessor.GetClimateAt(Blockentity.Pos, EnumGetClimateMode.NowValues);
|
||||||
|
rainfall = climate?.Rainfall ?? 0f;
|
||||||
|
temperature = climate?.Temperature ?? 20f;
|
||||||
|
|
||||||
|
// iterate from end to easily remove from list
|
||||||
|
for (int i = activeBees.Count - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var bee = activeBees[i];
|
||||||
|
|
||||||
|
if (Api.Side == EnumAppSide.Client)
|
||||||
|
{
|
||||||
|
bee.Step(dt, windSpeed);
|
||||||
|
if (Config.Instance.ReceiveParticles && Api.World is IClientWorldAccessor clientWorld)
|
||||||
|
BeeVisualParticleRenderer.Spawn(clientWorld, Blockentity.Pos, bee);
|
||||||
|
}
|
||||||
|
if (Api.Side == EnumAppSide.Server)
|
||||||
|
{
|
||||||
|
for (var j = 0; j < SERVER_TICK_FREQUENCY_DECREASE; j++)
|
||||||
|
bee.Step(dt / SERVER_TICK_FREQUENCY_DECREASE, windSpeed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bee.ShouldBeDespawned && modSystem != null)
|
||||||
|
{
|
||||||
|
activeBees.RemoveAt(i);
|
||||||
|
modSystem.GlobalActiveBees--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeSinceLastSpawn += dt;
|
||||||
|
if (Api.Side == EnumAppSide.Server)
|
||||||
|
TrySpawnNewBee(TargetParticleCount, nearbyPlantPositions);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TrySpawnNewBee(int targetParticleCount, IEnumerable<Vector3> nearbyPlantPositions)
|
||||||
|
{
|
||||||
|
if (TimeSinceLastSpawn < spawn_cooldown_seconds)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!nearbyPlantPositions.Any())
|
||||||
|
return;
|
||||||
|
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
|
||||||
|
var sunPos = Api.World.Calendar.GetSunPosition(new Vec3d(Blockentity.Pos.X, Blockentity.Pos.Y, Blockentity.Pos.Z), Api.World.Calendar.TotalDays);
|
||||||
|
float sunAltitudeDegrees = MathF.Asin(sunPos.Y) * 180f / GameMath.PI;
|
||||||
|
if (sunPos.Y <= cfg.SunAltitudeMinDegrees)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (rainfall >= cfg.RainfallSpawnStopThreshold)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int maxGlobal = cfg.MaxGlobalRoamingBees;
|
||||||
|
if (modSystem?.ServerChannel is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (modSystem.GlobalActiveBees >= maxGlobal)
|
||||||
|
return;
|
||||||
|
|
||||||
|
int desiredParticleCount = targetParticleCount;
|
||||||
|
if (activeBees.Count >= desiredParticleCount)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float sunAltitudeModifier = 1 - Math.Clamp((sunAltitudeDegrees - cfg.SunAltitudeMinDegrees) / cfg.SunAltitudeRangeDegrees, 0f, 1f);
|
||||||
|
float sunAltitudeCooldownPenalty = sunAltitudeModifier * cfg.MaxSunAltitudeCooldownPenalty;
|
||||||
|
|
||||||
|
float rainfallFactor = rainfall / cfg.RainfallSpawnStopThreshold;
|
||||||
|
float rainfallCooldownPenalty = rainfallFactor * cfg.MaxRainfallCooldownPenalty;
|
||||||
|
float spawnCooldownSeconds = rainfallCooldownPenalty + sunAltitudeCooldownPenalty;
|
||||||
|
|
||||||
|
float temperatureRange = cfg.MaxTemperatureParticleSpawn - cfg.MinTemperatureParticleSpawn;
|
||||||
|
float temperatureFactor = Math.Clamp((cfg.MaxTemperatureParticleSpawn - temperature) / temperatureRange, 0f, 1f);
|
||||||
|
|
||||||
|
var randomChance = random.NextDouble() > temperatureFactor && random.NextDouble() > sunAltitudeModifier && random.NextDouble() > 0.3f;
|
||||||
|
if (!randomChance)
|
||||||
|
{
|
||||||
|
var noise = random.NextDouble() * 2;
|
||||||
|
TimeSinceLastSpawn = -(float)(random.NextDouble() * spawnCooldownSeconds + noise);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var facing = GetFacing();
|
||||||
|
var entrancePos = GetEntrancePosition(facing);
|
||||||
|
var frontDirection = GetFrontDirection(facing);
|
||||||
|
var path = BeePathGeneration.GeneratePath(Api.World.BlockAccessor, Blockentity.Pos, entrancePos, frontDirection, nearbyPlantPositions);
|
||||||
|
if (path is null || path.Length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var message = new BeeSpawnPacket
|
||||||
|
{
|
||||||
|
HivePosition = Blockentity.Pos,
|
||||||
|
Path = [.. path.Select(x => BeePlannedPathPointContract.FromPoint(x))],
|
||||||
|
Facing = facing,
|
||||||
|
Role = BeeRole.Forager,
|
||||||
|
TimeElapsedSeconds = Api.World.Calendar.ElapsedSeconds,
|
||||||
|
EntrancePosition = entrancePos,
|
||||||
|
DespawnPosition = entrancePos,
|
||||||
|
};
|
||||||
|
activeBees.Add(new InternalBeeParticle(entrancePos, frontDirection, path, BeeRole.Forager, entrancePos, message));
|
||||||
|
modSystem.GlobalActiveBees++;
|
||||||
|
TimeSinceLastSpawn = 0f;
|
||||||
|
|
||||||
|
modSystem.ServerChannel.BroadcastPacket(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasRequiredLangstrothBase()
|
||||||
|
{
|
||||||
|
if (Blockentity is not BlockEntityDisplay blockEntityDisplay)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return blockEntityDisplay.Inventory.Any(x => x.Itemstack?.Block?.Code?.Path?.Contains("langstrothbase", StringComparison.OrdinalIgnoreCase) == true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Vector3 GetEntrancePosition() => GetEntrancePosition(GetFacing());
|
||||||
|
public Vector3 GetFrontDirection() => GetFrontDirection(GetFacing());
|
||||||
|
|
||||||
|
private Vector3 GetEntrancePosition(string facing)
|
||||||
|
{
|
||||||
|
if (entrancePositions != null)
|
||||||
|
{
|
||||||
|
if (entrancePositions.TryGetValue(facing, out var entrance))
|
||||||
|
return entrance;
|
||||||
|
if (entrancePositions.TryGetValue("*", out entrance))
|
||||||
|
return entrance;
|
||||||
|
}
|
||||||
|
|
||||||
|
return facing switch
|
||||||
|
{
|
||||||
|
"north" => new Vector3(0.5f, 0.2f, 0.9f),
|
||||||
|
"east" => new Vector3(0.1f, 0.2f, 0.5f),
|
||||||
|
"south" => new Vector3(0.5f, 0.2f, 0.1f),
|
||||||
|
"west" => new Vector3(0.9f, 0.2f, 0.5f),
|
||||||
|
_ => new Vector3(0.5f, 0.2f, 0.9f)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3[] GetNearbyPlantPositions()
|
||||||
|
{
|
||||||
|
int radius = Config.Instance.BeeRoamingRadius;
|
||||||
|
if (radius <= 0)
|
||||||
|
{
|
||||||
|
radius = entityAttributeSnapshot.GetInt(RADIUS_ATTRIBUTE, 10);
|
||||||
|
if (radius <= 0)
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
var plantPositions = new List<Vector3>();
|
||||||
|
var plantRegistry = Api.GetPlantPositionRegistry();
|
||||||
|
if (plantRegistry is null)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
var (flowerCount, cropCount, initialScanProgress, rescanProgress) = plantRegistry.GetPlantCountsNearPosition(Blockentity.Pos, radius);
|
||||||
|
FlowerCount = flowerCount;
|
||||||
|
CropCount = cropCount;
|
||||||
|
InitialScanProgress = initialScanProgress;
|
||||||
|
RescanProgress = rescanProgress;
|
||||||
|
|
||||||
|
var (flowers, crops, _, _) = plantRegistry.GetPlantsNearPosition(Blockentity.Pos, radius);
|
||||||
|
foreach (var pos in flowers.Concat(crops))
|
||||||
|
{
|
||||||
|
float relX = pos.X - Blockentity.Pos.X + 0.5f;
|
||||||
|
float relY = pos.Y - Blockentity.Pos.Y + 0.5f;
|
||||||
|
float relZ = pos.Z - Blockentity.Pos.Z + 0.5f;
|
||||||
|
plantPositions.Add(new Vector3(relX, relY, relZ));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [.. plantPositions];
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3 GetFrontDirection(string facing)
|
||||||
|
{
|
||||||
|
if (frontDirections != null)
|
||||||
|
{
|
||||||
|
if (frontDirections.TryGetValue(facing, out var frontDirection))
|
||||||
|
return frontDirection;
|
||||||
|
if (frontDirections.TryGetValue("*", out frontDirection))
|
||||||
|
return frontDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
return facing switch
|
||||||
|
{
|
||||||
|
"north" => new Vector3(0f, 0f, 1f),
|
||||||
|
"east" => new Vector3(-1f, 0f, 0f),
|
||||||
|
"south" => new Vector3(0f, 0f, -1f),
|
||||||
|
"west" => new Vector3(1f, 0f, 0f),
|
||||||
|
_ => new Vector3(0f, 0f, 1f)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetFacing()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrWhiteSpace(facingVariantKey))
|
||||||
|
{
|
||||||
|
var block = Blockentity.Block;
|
||||||
|
if (block?.Variant?.ContainsKey(facingVariantKey) == true)
|
||||||
|
return block.Variant[facingVariantKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
// omnidirectional block, randomly pick from available entrance directions
|
||||||
|
if (entrancePositions is { Count: > 0 })
|
||||||
|
{
|
||||||
|
var keys = entrancePositions.Keys.Where(k => k != "*").ToList();
|
||||||
|
if (keys.Count > 0)
|
||||||
|
return keys[random.Next(keys.Count)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return "north";
|
||||||
|
}
|
||||||
|
}
|
||||||
83
RoamingBees/RoamingBees/ChatCommands.cs
Normal file
83
RoamingBees/RoamingBees/ChatCommands.cs
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
using OrekiWoofsBees.Common.Configs;
|
||||||
|
using OrekiWoofsBees.Common;
|
||||||
|
using Vintagestory.API.Client;
|
||||||
|
using Vintagestory.API.Config;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.Server;
|
||||||
|
|
||||||
|
namespace RoamingBees;
|
||||||
|
|
||||||
|
public partial class RoamingBeesModSystem
|
||||||
|
{
|
||||||
|
private const string lang_domain = "roamingbees";
|
||||||
|
private const string config_filename = "RoamingBees.json";
|
||||||
|
|
||||||
|
public void SetupClientCommands(ICoreClientAPI api)
|
||||||
|
{
|
||||||
|
var rootCommand = api.ChatCommands.Create("roamingbees");
|
||||||
|
ConfigCommands.Register(rootCommand, api.ChatCommands, lang_domain, serverSide: false, () => Config.Instance, SaveClientConfig);
|
||||||
|
|
||||||
|
rootCommand
|
||||||
|
.BeginSubCommand("debugpaths")
|
||||||
|
.WithDescription(Lang.Get($"{lang_domain}:debugpaths-desc"))
|
||||||
|
.WithArgs(api.ChatCommands.Parsers.OptionalIntRange("value", 0, 1))
|
||||||
|
.HandleWith(HandleDebugPathsCommand)
|
||||||
|
.EndSubCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetupServerCommands(ICoreServerAPI api)
|
||||||
|
{
|
||||||
|
var rootCommand = api.ChatCommands.Create("roamingbees");
|
||||||
|
ConfigCommands.Register(rootCommand, api.ChatCommands, lang_domain, serverSide: true, () => Config.Instance, SaveServerConfig);
|
||||||
|
|
||||||
|
rootCommand
|
||||||
|
.BeginSubCommand("PlantReg")
|
||||||
|
.BeginSubCommand("BlocksPerTick")
|
||||||
|
.WithDescription(Lang.Get($"{lang_domain}:plantreg-blockpertick-desc"))
|
||||||
|
.WithArgs(api.ChatCommands.Parsers.OptionalIntRange("value", 0, 1000))
|
||||||
|
.HandleWith(HandlePlantRegBlockPerTickCommand)
|
||||||
|
.EndSubCommand()
|
||||||
|
.EndSubCommand();
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextCommandResult HandleDebugPathsCommand(TextCommandCallingArgs args)
|
||||||
|
{
|
||||||
|
if (args.Parsers[0].IsMissing)
|
||||||
|
return TextCommandResult.Success($"debugpaths={(DebugPathsEnabled ? 1 : 0)}");
|
||||||
|
|
||||||
|
if (args.Parsers[0].GetValue() is not int value)
|
||||||
|
return TextCommandResult.Error(Lang.Get($"{lang_domain}:debugpaths-parse-error"));
|
||||||
|
|
||||||
|
SetDebugPathsEnabled(value == 1);
|
||||||
|
return TextCommandResult.Success($"debugpaths={(DebugPathsEnabled ? 1 : 0)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveClientConfig(bool _)
|
||||||
|
{
|
||||||
|
var temp = api?.LoadModConfig<Config>(config_filename) ?? new Config();
|
||||||
|
temp.ReceiveParticles = Config.Instance.ReceiveParticles;
|
||||||
|
api?.StoreModConfig(temp, config_filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveServerConfig(bool _)
|
||||||
|
{
|
||||||
|
api?.StoreModConfig(Config.Instance, config_filename);
|
||||||
|
serverApi?.Network.GetChannel(CONFIG_CHANNEL_NAME).BroadcastPacket(Config.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TextCommandResult HandlePlantRegBlockPerTickCommand(TextCommandCallingArgs args)
|
||||||
|
{
|
||||||
|
var registry = api?.ModLoader.GetModSystem<PlantPositionRegistryModSystem2>();
|
||||||
|
if (registry == null)
|
||||||
|
return TextCommandResult.Error(Lang.Get($"{lang_domain}:plantreg-unavailable"));
|
||||||
|
|
||||||
|
if (args.Parsers[0].IsMissing)
|
||||||
|
return TextCommandResult.Success($"PlantReg BlocksPerTick={registry.BlocksPerTick}");
|
||||||
|
|
||||||
|
if (args.Parsers[0].GetValue() is not int value)
|
||||||
|
return TextCommandResult.Error(Lang.Get($"{lang_domain}:plantreg-blockpertick-parse-error"));
|
||||||
|
|
||||||
|
registry.BlocksPerTick = value;
|
||||||
|
return TextCommandResult.Success($"PlantReg BlocksPerTick={registry.BlocksPerTick}");
|
||||||
|
}
|
||||||
|
}
|
||||||
113
RoamingBees/RoamingBees/Config.cs
Normal file
113
RoamingBees/RoamingBees/Config.cs
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
using OrekiWoofsBees.Common.Configs;
|
||||||
|
using ProtoBuf;
|
||||||
|
|
||||||
|
namespace RoamingBees;
|
||||||
|
|
||||||
|
[ProtoContract(SkipConstructor = true)]
|
||||||
|
public class Config
|
||||||
|
{
|
||||||
|
public static Config Instance { get; internal set; } = new();
|
||||||
|
|
||||||
|
// client-side
|
||||||
|
[ProtoMember(16)]
|
||||||
|
[ConfigCommand(serverSide: false)]
|
||||||
|
public bool ReceiveParticles { get; set; } = true;
|
||||||
|
|
||||||
|
// general
|
||||||
|
[ProtoMember(17)]
|
||||||
|
[ConfigCommand(serverSide: true)]
|
||||||
|
public bool EnableOnVanillaSkeps { get; set; } = true;
|
||||||
|
|
||||||
|
[ProtoMember(18)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 100)]
|
||||||
|
public int RoamingBeesPerVanillaSkep { get; set; } = 15;
|
||||||
|
|
||||||
|
[ProtoMember(19)]
|
||||||
|
[ConfigCommand(serverSide: true)]
|
||||||
|
public bool EnableOnFgcCeramic { get; set; } = true;
|
||||||
|
|
||||||
|
[ProtoMember(20)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 100)]
|
||||||
|
public int RoamingBeesPerFgcCeramic { get; set; } = 15;
|
||||||
|
|
||||||
|
[ProtoMember(21)]
|
||||||
|
[ConfigCommand(serverSide: true)]
|
||||||
|
public bool EnableOnFgcLangstroth { get; set; } = true;
|
||||||
|
|
||||||
|
[ProtoMember(22)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 100)]
|
||||||
|
public int RoamingBeesPerFgcLangstroth { get; set; } = 25;
|
||||||
|
|
||||||
|
[ProtoMember(1)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 200)]
|
||||||
|
public int BeeRoamingRadius { get; set; } = 0;
|
||||||
|
|
||||||
|
// swarms
|
||||||
|
[ProtoMember(23)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 50)]
|
||||||
|
public int BeesPerSwarmHovering { get; set; } = 15;
|
||||||
|
|
||||||
|
[ProtoMember(24)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 50)]
|
||||||
|
public int BeesPerSwarmTraveling { get; set; } = 15;
|
||||||
|
|
||||||
|
// weather effects
|
||||||
|
[ProtoMember(2)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 1)]
|
||||||
|
public float RainfallSpawnStopThreshold { get; set; } = 0.1f;
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 300)]
|
||||||
|
public float MaxRainfallCooldownPenalty { get; set; } = 60f;
|
||||||
|
|
||||||
|
[ProtoMember(4)]
|
||||||
|
[ConfigCommand(serverSide: true)]
|
||||||
|
public bool GreenhouseAffectsBeehive { get; set; } = true;
|
||||||
|
|
||||||
|
[ProtoMember(5)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = -20, Max = 40)]
|
||||||
|
public float MinTemperatureParticleSpawn { get; set; } = 8f;
|
||||||
|
|
||||||
|
[ProtoMember(6)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = -20, Max = 50)]
|
||||||
|
public float MaxTemperatureParticleSpawn { get; set; } = 18f;
|
||||||
|
|
||||||
|
[ProtoMember(7)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 1)]
|
||||||
|
public float OptimalTemperatureSpawnChance { get; set; } = 0.75f;
|
||||||
|
|
||||||
|
[ProtoMember(8)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 1)]
|
||||||
|
public float BaseWindFrequency { get; set; } = 0.1f;
|
||||||
|
|
||||||
|
[ProtoMember(9)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 20)]
|
||||||
|
public float WindFrequencyMultiplier { get; set; } = 4f;
|
||||||
|
|
||||||
|
[ProtoMember(10)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 1)]
|
||||||
|
public float BaseWindNoiseStrength { get; set; } = 0.2f;
|
||||||
|
|
||||||
|
[ProtoMember(11)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 5)]
|
||||||
|
public float MaxWindNoiseStrength { get; set; } = 1f;
|
||||||
|
|
||||||
|
[ProtoMember(12)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = -90, Max = 90)]
|
||||||
|
public float SunAltitudeMinDegrees { get; set; } = -5f;
|
||||||
|
|
||||||
|
[ProtoMember(13)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 180)]
|
||||||
|
public float SunAltitudeRangeDegrees { get; set; } = 10f;
|
||||||
|
|
||||||
|
[ProtoMember(14)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 0, Max = 300)]
|
||||||
|
public float MaxSunAltitudeCooldownPenalty { get; set; } = 30f;
|
||||||
|
|
||||||
|
// particles
|
||||||
|
[ProtoMember(15)]
|
||||||
|
[ConfigCommand(serverSide: true, Min = 1, Max = 2000)]
|
||||||
|
public int MaxGlobalRoamingBees { get; set; } = 200;
|
||||||
|
// hidden
|
||||||
|
public bool EyesAndAntennaeEnabled { get; set; } = false;
|
||||||
|
}
|
||||||
100
RoamingBees/RoamingBees/ModConfigSetup.cs
Normal file
100
RoamingBees/RoamingBees/ModConfigSetup.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using ConfigLib;
|
||||||
|
using System;
|
||||||
|
using Vintagestory.API.Client;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.Server;
|
||||||
|
|
||||||
|
namespace RoamingBees;
|
||||||
|
|
||||||
|
public partial class RoamingBeesModSystem : ModSystem
|
||||||
|
{
|
||||||
|
private ConfigLibModSystem? configLibSystem;
|
||||||
|
|
||||||
|
private void SubscribeToConfigChange(ICoreAPI api)
|
||||||
|
{
|
||||||
|
configLibSystem = api.ModLoader.GetModSystem<ConfigLibModSystem>();
|
||||||
|
if (configLibSystem == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
configLibSystem.SettingChanged += OnSettingChanged;
|
||||||
|
configLibSystem.ConfigsLoaded += OnConfigsLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UnsubscribeFromConfigChange()
|
||||||
|
{
|
||||||
|
if (configLibSystem is null)
|
||||||
|
return;
|
||||||
|
configLibSystem.SettingChanged -= OnSettingChanged;
|
||||||
|
configLibSystem.ConfigsLoaded -= OnConfigsLoaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConfigsLoaded()
|
||||||
|
{
|
||||||
|
configLibSystem?.GetConfig("roamingbees")?.AssignSettingsValues(Config.Instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSettingChanged(string domain, IConfig config, ISetting setting)
|
||||||
|
{
|
||||||
|
if (domain != "roamingbees")
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (api != null)
|
||||||
|
Config.Instance = api.LoadModConfig<Config>("RoamingBees.json") ?? Config.Instance;
|
||||||
|
setting.AssignSettingValue(Config.Instance);
|
||||||
|
serverApi?.Network.GetChannel(CONFIG_CHANNEL_NAME).BroadcastPacket(Config.Instance);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Mod.Logger.Error(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeServerConfig(ICoreServerAPI api)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Config.Instance = api.LoadModConfig<Config>("RoamingBees.json");
|
||||||
|
Config.Instance ??= new Config();
|
||||||
|
api.StoreModConfig(Config.Instance, "RoamingBees.json");
|
||||||
|
api.Event.PlayerJoin += OnPlayerJoin;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Mod.Logger.Error("Could not load config! Loading default settings instead.");
|
||||||
|
Mod.Logger.Error(e);
|
||||||
|
Config.Instance = new Config();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeClientConfig(ICoreClientAPI api)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Config? clientConfig = api.LoadModConfig<Config>("RoamingBees.json");
|
||||||
|
if (clientConfig != null)
|
||||||
|
Config.Instance.ReceiveParticles = clientConfig.ReceiveParticles;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Mod.Logger.Error("Could not load config! Loading default settings instead.");
|
||||||
|
Mod.Logger.Error(e);
|
||||||
|
Config.Instance = new Config();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlayerJoin(IServerPlayer byPlayer)
|
||||||
|
{
|
||||||
|
serverApi!.Network.GetChannel(CONFIG_CHANNEL_NAME).SendPacket(Config.Instance, byPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnConfigReceivedFromServer(Config packet)
|
||||||
|
{
|
||||||
|
var currentConfig = Config.Instance;
|
||||||
|
Config.Instance = packet;
|
||||||
|
if (currentConfig is null)
|
||||||
|
return;
|
||||||
|
Config.Instance.ReceiveParticles = currentConfig.ReceiveParticles;
|
||||||
|
}
|
||||||
|
}
|
||||||
432
RoamingBees/RoamingBees/Particles/BeePathGeneration.cs
Normal file
432
RoamingBees/RoamingBees/Particles/BeePathGeneration.cs
Normal file
@@ -0,0 +1,432 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.Datastructures;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
using Vintagestory.GameContent;
|
||||||
|
|
||||||
|
namespace RoamingBees.Particles;
|
||||||
|
|
||||||
|
public static class BeePathGeneration
|
||||||
|
{
|
||||||
|
private const int max_plants_low = 2;
|
||||||
|
private const int max_plants_high = 14;
|
||||||
|
private static Random random { get; } = new();
|
||||||
|
private static List<BeePlannedPathPoint> temp_list { get; } = [];
|
||||||
|
private static List<Vector3> temp_plants { get; } = [];
|
||||||
|
private static HashSet<Vector3> used_plants { get; } = [];
|
||||||
|
internal static Vector3[] Offsets { get; }
|
||||||
|
|
||||||
|
static BeePathGeneration()
|
||||||
|
{
|
||||||
|
Offsets = [
|
||||||
|
new Vector3(1, 0, 0),
|
||||||
|
new Vector3(-1, 0, 0),
|
||||||
|
new Vector3(0, 0, 1),
|
||||||
|
new Vector3(0, 0, -1),
|
||||||
|
new Vector3(1, 0, 1),
|
||||||
|
new Vector3(1, 0, -1),
|
||||||
|
new Vector3(-1, 0, 1),
|
||||||
|
new Vector3(-1, 0, -1),
|
||||||
|
new Vector3(0, 1.5f, 0),
|
||||||
|
new Vector3(0, -1.5f, 0),
|
||||||
|
new Vector3(2, 0, 0),
|
||||||
|
new Vector3(-2, 0, 0),
|
||||||
|
new Vector3(0, 0, 2),
|
||||||
|
new Vector3(0, 0, -2),
|
||||||
|
new Vector3(2, 0, 1),
|
||||||
|
new Vector3(2, 0, -1),
|
||||||
|
new Vector3(-2, 0, 1),
|
||||||
|
new Vector3(-2, 0, -1),
|
||||||
|
new Vector3(1, 0, 2),
|
||||||
|
new Vector3(1, 0, -2),
|
||||||
|
new Vector3(-1, 0, 2),
|
||||||
|
new Vector3(-1, 0, -2),
|
||||||
|
new Vector3(3, 0, 0),
|
||||||
|
new Vector3(-3, 0, 0),
|
||||||
|
new Vector3(0, 0, 3),
|
||||||
|
new Vector3(0, 0, -3),
|
||||||
|
new Vector3(0, 3, 0),
|
||||||
|
new Vector3(0, -3, 0),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BeePlannedPathPoint[] GeneratePath(
|
||||||
|
IBlockAccessor blockAccessor,
|
||||||
|
BlockPos hiveWorldPos,
|
||||||
|
Vector3 beehiveEntrancePosition,
|
||||||
|
Vector3 frontDirection,
|
||||||
|
IEnumerable<Vector3> plantPositions)
|
||||||
|
{
|
||||||
|
temp_list.Clear();
|
||||||
|
temp_plants.Clear();
|
||||||
|
used_plants.Clear();
|
||||||
|
|
||||||
|
foreach (var plant in plantPositions)
|
||||||
|
temp_plants.Add(plant);
|
||||||
|
|
||||||
|
var plant_count = random.Next(max_plants_low, max_plants_high + 1);
|
||||||
|
for (var i = 0; i < plant_count; i++)
|
||||||
|
{
|
||||||
|
var lastPosition = temp_list.Count > 0 ? temp_list[^1].Position : GetFrontOfEntrance(beehiveEntrancePosition, frontDirection);
|
||||||
|
foreach (var plantPathPoint in PickTargetAndGeneratePathPoints(blockAccessor, hiveWorldPos, lastPosition))
|
||||||
|
temp_list.Add(plantPathPoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (temp_list.Count == 0)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
foreach (var returnPathPoint in GetReturnPath(blockAccessor, hiveWorldPos, temp_list.Last().Position, beehiveEntrancePosition, frontDirection))
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(returnPathPoint, true, false));
|
||||||
|
|
||||||
|
temp_list[^1] = temp_list[^1] with { IsSlowdown = true };
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(beehiveEntrancePosition, false, true));
|
||||||
|
|
||||||
|
return [.. temp_list];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryGetTargetBoxAtWorldBlock(IBlockAccessor blockAccessor, BlockPos blockPosWorld, out Cuboidf targetBox)
|
||||||
|
{
|
||||||
|
targetBox = new Cuboidf();
|
||||||
|
|
||||||
|
var blockCenter = new Vector3(blockPosWorld.X + 0.5f, blockPosWorld.Y + 0.5f, blockPosWorld.Z + 0.5f);
|
||||||
|
var (plantOffset, plantSize) = GetPlantOffsetAndSize(blockAccessor, blockCenter, new BlockPos(0, 0, 0));
|
||||||
|
if (plantOffset is null || plantSize is null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var adjustedCenter = AdjustCenter(blockCenter, plantOffset.Value, plantSize.Value);
|
||||||
|
var halfSize = plantSize.Value * 0.5f;
|
||||||
|
|
||||||
|
targetBox = new Cuboidf(
|
||||||
|
adjustedCenter.X - halfSize.X,
|
||||||
|
adjustedCenter.Y - halfSize.Y,
|
||||||
|
adjustedCenter.Z - halfSize.Z,
|
||||||
|
adjustedCenter.X + halfSize.X,
|
||||||
|
adjustedCenter.Y + halfSize.Y,
|
||||||
|
adjustedCenter.Z + halfSize.Z
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 AdjustCenter(Vector3 blockCenter, Vector3 plantOffset, Vector3 plantSize)
|
||||||
|
{
|
||||||
|
var blockPosFloored = new Vector3((int)Math.Floor(blockCenter.X), (int)Math.Floor(blockCenter.Y), (int)Math.Floor(blockCenter.Z));
|
||||||
|
return new Vector3(
|
||||||
|
blockPosFloored.X + plantOffset.X,
|
||||||
|
blockPosFloored.Y + plantOffset.Y + plantSize.Y * 0.5f,
|
||||||
|
blockPosFloored.Z + plantOffset.Z);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (Vector3?, Vector3?) GetPlantOffsetAndSize(IBlockAccessor blockAccessor, Vector3 blockCenter, BlockPos hiveWorldPos)
|
||||||
|
{
|
||||||
|
var blockPos = new BlockPos((int)Math.Floor(blockCenter.X), (int)Math.Floor(blockCenter.Y), (int)Math.Floor(blockCenter.Z));
|
||||||
|
var blockPosWorld = blockPos + hiveWorldPos;
|
||||||
|
|
||||||
|
var block = blockAccessor.GetBlock(blockPosWorld);
|
||||||
|
var attributes = block.GetAttributes(blockAccessor, blockPos);
|
||||||
|
|
||||||
|
if (TryGetPlantContainerOffsetAndSize(blockAccessor, blockPosWorld, block, attributes, out var containerOffset, out var containerSize))
|
||||||
|
return (containerOffset, containerSize);
|
||||||
|
|
||||||
|
if (TryGetBlockPlantOffsetAndSize(blockAccessor, blockPosWorld, block, attributes, out var plantOffset, out var plantSize))
|
||||||
|
return (plantOffset, plantSize);
|
||||||
|
|
||||||
|
return (null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetPlantContainerOffsetAndSize(
|
||||||
|
IBlockAccessor blockAccessor,
|
||||||
|
BlockPos blockPosWorld,
|
||||||
|
Block block,
|
||||||
|
JsonObject attributes,
|
||||||
|
out Vector3 offset,
|
||||||
|
out Vector3 size)
|
||||||
|
{
|
||||||
|
offset = default;
|
||||||
|
size = default;
|
||||||
|
|
||||||
|
if (block.Class != "BlockPlantContainer")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Cuboidf[] containerSelectionBoxes = block.GetSelectionBoxes(blockAccessor, blockPosWorld);
|
||||||
|
if (containerSelectionBoxes.Length == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Cuboidf containerSelectionBox = containerSelectionBoxes.First();
|
||||||
|
var fillHeight = attributes["fillHeight"]?.AsFloat(0.5f) ?? 0.5f;
|
||||||
|
var sitHeight = attributes["sitHeight"]?.AsFloat(Math.Min(0.9f, fillHeight + 0.2f)) ?? Math.Min(0.9f, fillHeight + 0.2f);
|
||||||
|
|
||||||
|
var targetMinY = Math.Clamp(fillHeight + 0.05f, 0f, 0.95f);
|
||||||
|
var targetMaxY = Math.Clamp(sitHeight + 0.1f, targetMinY + 0.05f, 0.98f);
|
||||||
|
var targetHeight = targetMaxY - targetMinY;
|
||||||
|
|
||||||
|
offset = new Vector3((float)containerSelectionBox.Center.X, targetMinY, (float)containerSelectionBox.Center.Z);
|
||||||
|
size = new Vector3(containerSelectionBox.Width, targetHeight, containerSelectionBox.Length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryGetBlockPlantOffsetAndSize(
|
||||||
|
IBlockAccessor blockAccessor,
|
||||||
|
BlockPos blockPosWorld,
|
||||||
|
Block block,
|
||||||
|
JsonObject attributes,
|
||||||
|
out Vector3 offset,
|
||||||
|
out Vector3 size)
|
||||||
|
{
|
||||||
|
offset = default;
|
||||||
|
size = default;
|
||||||
|
|
||||||
|
if (block is not BlockPlant blockPlant)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (blockPlant.RandomDrawOffset == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Cuboidf[] selectionBoxes = blockPlant.GetSelectionBoxes(blockAccessor, blockPosWorld);
|
||||||
|
if (selectionBoxes.Length == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Cuboidf selectionBox = selectionBoxes.First();
|
||||||
|
var height = blockPlant.drawnHeight / 32f;
|
||||||
|
var sitHeightAttr = attributes["sitHeight"];
|
||||||
|
if (sitHeightAttr != null)
|
||||||
|
{
|
||||||
|
var sitHeight = sitHeightAttr.AsFloat(1);
|
||||||
|
height *= sitHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = new Vector3((float)selectionBox.Center.X, 0f, (float)selectionBox.Center.Z);
|
||||||
|
size = new Vector3(selectionBox.Width, height, selectionBox.Length);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<BeePlannedPathPoint> PickTargetAndGeneratePathPoints(IBlockAccessor blockAccessor, BlockPos hiveWorldPos, Vector3 lastPosition)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < 2; i++)
|
||||||
|
{
|
||||||
|
var nearby_only = i == 1;
|
||||||
|
if (!TryPickPlantPosition(lastPosition, nearby_only, out var blockCenter))
|
||||||
|
yield break;
|
||||||
|
|
||||||
|
var (plantOffset, plantSize) = GetPlantOffsetAndSize(blockAccessor, blockCenter, hiveWorldPos);
|
||||||
|
if (plantSize is not null && plantOffset is not null)
|
||||||
|
blockCenter = AdjustCenter(blockCenter, plantOffset.Value, plantSize.Value);
|
||||||
|
|
||||||
|
var size = plantSize ?? new Vector3(1, 1, 1);
|
||||||
|
if (TryPickPlantTarget(blockAccessor, hiveWorldPos, lastPosition, blockCenter, size, out var target, out var waypoint, true))
|
||||||
|
{
|
||||||
|
used_plants.Add(blockCenter);
|
||||||
|
if (waypoint is not null)
|
||||||
|
yield return new BeePlannedPathPoint(waypoint.Value, true, false);
|
||||||
|
foreach (var plantTarget in GetPlantTargets(blockCenter, size, target))
|
||||||
|
yield return new BeePlannedPathPoint(plantTarget, false, false);
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryPickPlantPosition(Vector3 lastPosition, bool nearbyOnly, out Vector3 blockCenter)
|
||||||
|
{
|
||||||
|
blockCenter = Vector3.Zero;
|
||||||
|
int valid_count = 0;
|
||||||
|
|
||||||
|
for (var i = 0; i < temp_plants.Count; i++)
|
||||||
|
{
|
||||||
|
var plant = temp_plants[i];
|
||||||
|
if (used_plants.Contains(plant))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (nearbyOnly && Vector3.Distance(lastPosition, plant) > 5f)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
valid_count++;
|
||||||
|
if (random.Next(valid_count) == 0)
|
||||||
|
blockCenter = plant;
|
||||||
|
}
|
||||||
|
|
||||||
|
return valid_count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Vector3> GetPlantTargets(Vector3 blockCenter, Vector3 plantSize, Vector3 firstTarget)
|
||||||
|
{
|
||||||
|
yield return firstTarget;
|
||||||
|
|
||||||
|
var total_targets = random.Next(3, 10);
|
||||||
|
for (var i = 1; i < total_targets; i++)
|
||||||
|
yield return GetRandomPointInPlantBlock(blockCenter, plantSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryPickPlantTarget(IBlockAccessor blockAccessor, BlockPos hiveWorldPos, Vector3 lastPosition, Vector3 blockCenter, Vector3 plantSize, out Vector3 target, out Vector3? waypoint, bool biasUpwards = false)
|
||||||
|
{
|
||||||
|
waypoint = null;
|
||||||
|
target = GetRandomPointInPlantBlock(blockCenter, plantSize, biasUpwards);
|
||||||
|
|
||||||
|
if (HasLineOfSight(blockAccessor, hiveWorldPos, lastPosition, target))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (TryFindWaypoint(blockAccessor, hiveWorldPos, lastPosition, target, out var waypointPosition))
|
||||||
|
{
|
||||||
|
waypoint = waypointPosition;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 GetRandomPointInPlantBlock(Vector3 blockCenter, Vector3 plantSize, bool biasUpwards = false)
|
||||||
|
{
|
||||||
|
double horizontal_range_x = plantSize.X;
|
||||||
|
double horizontal_half_range_x = horizontal_range_x / 2d;
|
||||||
|
float offsetX = (float)(random.NextDouble() * horizontal_range_x - horizontal_half_range_x);
|
||||||
|
|
||||||
|
double horizontal_range_z = plantSize.Z;
|
||||||
|
double horizontal_half_range_z = horizontal_range_z / 2d;
|
||||||
|
float offsetZ = (float)(random.NextDouble() * horizontal_range_z - horizontal_half_range_z);
|
||||||
|
|
||||||
|
double vertical_range = plantSize.Y;
|
||||||
|
double vertical_half_range = vertical_range / 2d;
|
||||||
|
float offsetY = biasUpwards
|
||||||
|
? (float)vertical_half_range
|
||||||
|
: (float)(random.NextDouble() * vertical_range - vertical_half_range);
|
||||||
|
return blockCenter + new Vector3(offsetX, offsetY, offsetZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool HasLineOfSight(IBlockAccessor blockAccessor, BlockPos hiveWorldPos, Vector3 from, Vector3 to)
|
||||||
|
{
|
||||||
|
var direction = to - from;
|
||||||
|
float distance = direction.Length();
|
||||||
|
if (distance < 0.1f)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
var normalized = Vector3.Normalize(direction);
|
||||||
|
var startMargin = 0.1f;
|
||||||
|
var endMargin = 0.1f;
|
||||||
|
if (distance <= startMargin + endMargin)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// The block the ray starts in should never count as an obstacle -
|
||||||
|
// the bee is already there (e.g. the swarm block itself).
|
||||||
|
var fromBlock = new Vec3i((int)MathF.Floor(from.X), (int)MathF.Floor(from.Y), (int)MathF.Floor(from.Z));
|
||||||
|
|
||||||
|
var stepSize = 0.4f;
|
||||||
|
var lateralOffset = 0.25f;
|
||||||
|
var lateral = new Vector3(-normalized.Z, 0f, normalized.X);
|
||||||
|
var lateralNormalized = lateral.Length() > 0.001f ? Vector3.Normalize(lateral) : Vector3.Zero;
|
||||||
|
var leftOffset = lateralNormalized * lateralOffset;
|
||||||
|
var rightOffset = lateralNormalized * -lateralOffset;
|
||||||
|
|
||||||
|
for (float t = startMargin; t <= distance - endMargin; t += stepSize)
|
||||||
|
{
|
||||||
|
var checkPosRelative = from + normalized * t;
|
||||||
|
if (IsBlockedAt(blockAccessor, hiveWorldPos, checkPosRelative.X, checkPosRelative.Y, checkPosRelative.Z, fromBlock))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (leftOffset != Vector3.Zero && IsBlockedAt(blockAccessor, hiveWorldPos, checkPosRelative.X + leftOffset.X, checkPosRelative.Y, checkPosRelative.Z + leftOffset.Z, fromBlock))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (rightOffset != Vector3.Zero && IsBlockedAt(blockAccessor, hiveWorldPos, checkPosRelative.X + rightOffset.X, checkPosRelative.Y, checkPosRelative.Z + rightOffset.Z, fromBlock))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsBlockedAt(IBlockAccessor blockAccessor, BlockPos anchor, float rx, float ry, float rz, Vec3i skipBlock)
|
||||||
|
{
|
||||||
|
var block = new Vec3i((int)MathF.Floor(rx), (int)MathF.Floor(ry), (int)MathF.Floor(rz));
|
||||||
|
if (block == skipBlock)
|
||||||
|
return false;
|
||||||
|
return BlocksLineOfSight(GetBlockAtRelative(blockAccessor, anchor, rx, ry, rz));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Block GetBlockAtRelative(IBlockAccessor blockAccessor, BlockPos anchor, float rx, float ry, float rz)
|
||||||
|
{
|
||||||
|
var pos = new BlockPos(anchor.X + (int)MathF.Floor(rx), anchor.Y + (int)MathF.Floor(ry), anchor.Z + (int)MathF.Floor(rz));
|
||||||
|
return blockAccessor.GetBlock(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool TryFindWaypoint(IBlockAccessor blockAccessor, BlockPos hiveWorldPos, Vector3 from, Vector3 to, out Vector3 waypoint)
|
||||||
|
{
|
||||||
|
var midPoint = (from + to) * 0.5f;
|
||||||
|
|
||||||
|
if (TryWaypointsAroundPosition(blockAccessor, hiveWorldPos, from, to, basePos: midPoint, Offsets, out waypoint))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (TryWaypointsAroundPosition(blockAccessor, hiveWorldPos, from, to, basePos: from, Offsets, out waypoint))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (TryWaypointsAroundPosition(blockAccessor, hiveWorldPos, from, to, basePos: to, Offsets, out waypoint))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool TryWaypointsAroundPosition(IBlockAccessor blockAccessor, BlockPos hiveWorldPos, Vector3 from, Vector3 to, Vector3 basePos, Vector3[] offsets, out Vector3 waypoint)
|
||||||
|
{
|
||||||
|
waypoint = Vector3.Zero;
|
||||||
|
|
||||||
|
foreach (var offset in offsets)
|
||||||
|
{
|
||||||
|
var waypointCandidate = GetOuterEdgePosition(basePos, offset);
|
||||||
|
|
||||||
|
if (HasLineOfSight(blockAccessor, hiveWorldPos, from, waypointCandidate) && HasLineOfSight(blockAccessor, hiveWorldPos, waypointCandidate, to))
|
||||||
|
{
|
||||||
|
waypoint = waypointCandidate;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Vector3 GetOuterEdgePosition(Vector3 basePos, Vector3 offset)
|
||||||
|
{
|
||||||
|
return basePos + new Vector3(
|
||||||
|
offset.X != 0 ? offset.X + 0.5f * MathF.Sign(offset.X) : 0,
|
||||||
|
offset.Y != 0 ? offset.Y + 0.5f * MathF.Sign(offset.Y) : 0,
|
||||||
|
offset.Z != 0 ? offset.Z + 0.5f * MathF.Sign(offset.Z) : 0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool BlocksLineOfSight(Block block)
|
||||||
|
{
|
||||||
|
if (block == null || block.BlockId == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (block.IsLiquid())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return block.CollisionBoxes != null && block.CollisionBoxes.Length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<Vector3> GetReturnPath(IBlockAccessor blockAccessor, BlockPos hiveWorldPos, Vector3 lastPosition, Vector3 beehiveEntrancePosition, Vector3 frontDirection)
|
||||||
|
{
|
||||||
|
var frontOfEntrance = GetFrontOfEntrance(beehiveEntrancePosition, frontDirection);
|
||||||
|
var targetPos = frontOfEntrance;
|
||||||
|
if (HasLineOfSight(blockAccessor, hiveWorldPos, lastPosition, targetPos))
|
||||||
|
{
|
||||||
|
yield return targetPos;
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryFindWaypoint(blockAccessor, hiveWorldPos, lastPosition, targetPos, out var waypoint))
|
||||||
|
{
|
||||||
|
yield return waypoint;
|
||||||
|
yield return targetPos;
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var pathPoint in temp_list.Reverse<BeePlannedPathPoint>())
|
||||||
|
yield return pathPoint.Position;
|
||||||
|
|
||||||
|
yield return frontOfEntrance;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static Vector3 GetFrontOfEntrance(Vector3 beehiveEntrancePosition, Vector3 frontDirection)
|
||||||
|
{
|
||||||
|
return beehiveEntrancePosition + Vector3.Normalize(frontDirection) * 0.5f + Vector3.UnitY * 0.2f;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
RoamingBees/RoamingBees/Particles/BeePlannedPathPoint.cs
Normal file
5
RoamingBees/RoamingBees/Particles/BeePlannedPathPoint.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace RoamingBees.Particles;
|
||||||
|
|
||||||
|
public readonly record struct BeePlannedPathPoint(Vector3 Position, bool IsIntermediate, bool IsSlowdown, Vector3? LookDirection = null);
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using OrekiWoofsBees.Common;
|
||||||
|
using ProtoBuf;
|
||||||
|
using System.Numerics;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace RoamingBees.Particles;
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public record struct BeePlannedPathPointContract(Vec3f Position, bool IsIntermediate, bool IsSlowdown, Vector3? LookDirection = null)
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public Vec3f Position = Position;
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public bool IsIntermediate = IsIntermediate;
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public bool IsSlowdown = IsSlowdown;
|
||||||
|
|
||||||
|
[ProtoMember(4)]
|
||||||
|
public Vec3f _LookDirection = LookDirection?.ToVec3f() ?? new();
|
||||||
|
public Vector3? LookDirection
|
||||||
|
{
|
||||||
|
readonly get => (_LookDirection.X * _LookDirection.X + _LookDirection.Y * _LookDirection.Y + _LookDirection.Z * _LookDirection.Z) > 0.0001f ? _LookDirection.ToVector3() : null;
|
||||||
|
init => _LookDirection = value.HasValue ? value.Value.ToVec3f() : new Vec3f();
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly BeePlannedPathPoint ToPoint()
|
||||||
|
{
|
||||||
|
return new BeePlannedPathPoint(new(Position.X, Position.Y, Position.Z), IsIntermediate, IsSlowdown, LookDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BeePlannedPathPointContract FromPoint(BeePlannedPathPoint p)
|
||||||
|
{
|
||||||
|
return new BeePlannedPathPointContract(new(p.Position.X, p.Position.Y, p.Position.Z), p.IsIntermediate, p.IsSlowdown, p.LookDirection);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
RoamingBees/RoamingBees/Particles/BeeRole.cs
Normal file
9
RoamingBees/RoamingBees/Particles/BeeRole.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
namespace RoamingBees.Particles;
|
||||||
|
|
||||||
|
public enum BeeRole
|
||||||
|
{
|
||||||
|
Forager,
|
||||||
|
Hovering,
|
||||||
|
Traveling,
|
||||||
|
Scouting,
|
||||||
|
}
|
||||||
34
RoamingBees/RoamingBees/Particles/BeeSpawnPacket.cs
Normal file
34
RoamingBees/RoamingBees/Particles/BeeSpawnPacket.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
using OrekiWoofsBees.Common;
|
||||||
|
using ProtoBuf;
|
||||||
|
using System.Numerics;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace RoamingBees.Particles;
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public record struct BeeSpawnPacket
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public required BlockPos HivePosition;
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public required BeePlannedPathPointContract[] Path;
|
||||||
|
|
||||||
|
[ProtoMember(3)]
|
||||||
|
public required string Facing;
|
||||||
|
|
||||||
|
[ProtoMember(4)]
|
||||||
|
public required BeeRole Role;
|
||||||
|
|
||||||
|
[ProtoMember(5)]
|
||||||
|
public Vec3f _EntrancePosition;
|
||||||
|
|
||||||
|
[ProtoMember(6)]
|
||||||
|
public Vec3f _DespawnPosition;
|
||||||
|
|
||||||
|
public required Vector3 EntrancePosition { readonly get => _EntrancePosition.ToVector3(); set => _EntrancePosition = value.ToVec3f(); }
|
||||||
|
public required Vector3 DespawnPosition { readonly get => _DespawnPosition.ToVector3(); set => _DespawnPosition = value.ToVec3f(); }
|
||||||
|
|
||||||
|
[ProtoMember(7)]
|
||||||
|
public required long TimeElapsedSeconds;
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
using OrekiWoofsBees.Common;
|
||||||
|
using RoamingBees.Particles.Catchup;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
using Vintagestory.API.Server;
|
||||||
|
|
||||||
|
namespace RoamingBees.Particles;
|
||||||
|
|
||||||
|
internal class BeeSpawnPacketDistributor(RoamingBeesModSystem modSystem, EnumAppSide appSide)
|
||||||
|
{
|
||||||
|
private readonly RoamingBeesModSystem modSystem = modSystem;
|
||||||
|
private readonly EnumAppSide appSide = appSide;
|
||||||
|
|
||||||
|
private Dictionary<Vector3, IBeeSpawnHandler> spawnHandlers { get; } = [];
|
||||||
|
private Dictionary<Vector3, IBeeSpawnCatchup> catchupHandlers { get; } = [];
|
||||||
|
|
||||||
|
public void Register(BlockPos position, object manager)
|
||||||
|
{
|
||||||
|
if (manager is IBeeSpawnHandler spawnHandler)
|
||||||
|
spawnHandlers.TryAdd(new(position.X, position.Y, position.Z), spawnHandler);
|
||||||
|
if (manager is IBeeSpawnCatchup beeSpawnCatchup)
|
||||||
|
catchupHandlers.TryAdd(new(position.X, position.Y, position.Z), beeSpawnCatchup);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Unregister(BlockPos position)
|
||||||
|
{
|
||||||
|
var pos = new Vector3(position.X, position.Y, position.Z);
|
||||||
|
spawnHandlers.Remove(pos);
|
||||||
|
catchupHandlers.Remove(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleBeeParticleSpawn(BeeSpawnPacket packet)
|
||||||
|
{
|
||||||
|
if (!Config.Instance.ReceiveParticles || appSide.HasFlag(EnumAppSide.Server))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (spawnHandlers.TryGetValue(new(packet.HivePosition.X, packet.HivePosition.Y, packet.HivePosition.Z), out var hive))
|
||||||
|
hive.HandleBeeParticleSpawn(packet);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void HandleBeeParticleCatchup(BeeCatchupPacket catchupPacket)
|
||||||
|
{
|
||||||
|
if (modSystem.Mod.Info.Version.Contains("dev"))
|
||||||
|
{
|
||||||
|
modSystem.Mod.Logger.Event($"HandleBeeParticleCatchup, Pos: {catchupPacket.HivePosition.ToVec3f().ToVector3()}, Count: {catchupPacket.SpawnPackets.Count()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Config.Instance.ReceiveParticles || appSide.HasFlag(EnumAppSide.Server))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!spawnHandlers.TryGetValue(new(catchupPacket.HivePosition.X, catchupPacket.HivePosition.Y, catchupPacket.HivePosition.Z), out var hive))
|
||||||
|
return;
|
||||||
|
foreach (var spawnPacket in catchupPacket.SpawnPackets)
|
||||||
|
hive.HandleBeeParticleSpawn(spawnPacket, catchup: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OnBeeCatchupRequest(IServerPlayer fromPlayer, BeeCatchupRequestPacket catchupRequest)
|
||||||
|
{
|
||||||
|
if (!appSide.HasFlag(EnumAppSide.Server))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!catchupHandlers.TryGetValue(new(catchupRequest.HivePosition.X, catchupRequest.HivePosition.Y, catchupRequest.HivePosition.Z), out var hive))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var spawnPackets = hive.ActiveBeesPackets.ToList();
|
||||||
|
if (spawnPackets.Count is 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var message = new BeeCatchupPacket
|
||||||
|
{
|
||||||
|
HivePosition = catchupRequest.HivePosition,
|
||||||
|
SpawnPackets = spawnPackets,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (modSystem.Mod.Info.Version.Contains("dev"))
|
||||||
|
modSystem.Mod.Logger.Event($"OnBeeCatchupRequest, Pos: {catchupRequest.HivePosition.ToVec3f().ToVector3()}, Count: {message.SpawnPackets.Count()}");
|
||||||
|
|
||||||
|
modSystem.ServerChannel?.SendPacket(message, fromPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear() => spawnHandlers.Clear();
|
||||||
|
}
|
||||||
235
RoamingBees/RoamingBees/Particles/BeeVisualParticleRenderer.cs
Normal file
235
RoamingBees/RoamingBees/Particles/BeeVisualParticleRenderer.cs
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
using System.Numerics;
|
||||||
|
using Vintagestory.API.Client;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace RoamingBees.Particles;
|
||||||
|
|
||||||
|
public static class BeeVisualParticleRenderer
|
||||||
|
{
|
||||||
|
private static SimpleParticleProperties? _baseProperties;
|
||||||
|
private static SimpleParticleProperties BaseProperties
|
||||||
|
{
|
||||||
|
get => _baseProperties ??= new SimpleParticleProperties()
|
||||||
|
{
|
||||||
|
MinPos = new Vec3d(),
|
||||||
|
MinVelocity = new Vec3f(),
|
||||||
|
MinSize = 0.5f,
|
||||||
|
MaxSize = 0.5f,
|
||||||
|
LifeLength = 0.02f,
|
||||||
|
WithTerrainCollision = false,
|
||||||
|
ParticleModel = EnumParticleModel.Cube,
|
||||||
|
MinQuantity = 1,
|
||||||
|
AddQuantity = 0,
|
||||||
|
Color = ColorUtil.ToRgba(255, 180, 160, 0), // ARGB: alpha=255, yellow RGB
|
||||||
|
GravityEffect = 0f,
|
||||||
|
SelfPropelled = false,
|
||||||
|
RandomVelocityChange = false,
|
||||||
|
WindAffected = false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static float BodyOffsetDivider { get; set; } = 32f;
|
||||||
|
public static float HeadOffsetMultiplier { get; set; } = 1f;
|
||||||
|
public static float TailOffsetMultiplier { get; set; } = 1f;
|
||||||
|
public static float HeadVerticalOffsetMultiplier { get; set; } = 0.5f;
|
||||||
|
public static float TailVerticalOffsetMultiplier { get; set; } = 0.5f;
|
||||||
|
|
||||||
|
public static float WingSize { get; set; } = 0.28f;
|
||||||
|
public static float WingHorizontalOffsetMultiplier { get; set; } = 1f;
|
||||||
|
public static float WingForwardOffsetMultiplierWhenFlap { get; set; } = -2f;
|
||||||
|
public static float WingForwardOffsetMultiplierWhenRest { get; set; } = -1f;
|
||||||
|
public static float WingVerticalOffsetMultiplierWhenFlap { get; set; } = 0f;
|
||||||
|
public static float WingVerticalOffsetMultiplierWhenRest { get; set; } = 1f;
|
||||||
|
public static Vec3d WingPosRandomness { get; set; } = new Vec3d(1, 0, 1) * 0.005;
|
||||||
|
|
||||||
|
public static float EyeSize { get; set; } = 0.18f;
|
||||||
|
public static float EyeForwardOffsetMultiplier { get; set; } = 1.7f;
|
||||||
|
public static float EyeHorizontalOffsetMultiplier { get; set; } = 0.5f;
|
||||||
|
public static float EyeVerticalOffsetMultiplier { get; set; } = 0.6f;
|
||||||
|
|
||||||
|
public static float AntennaSize { get; set; } = 0.12f;
|
||||||
|
public static int AntennaSegments { get; set; } = 5;
|
||||||
|
public static float AntennaForwardOffsetMultiplier { get; set; } = 1.5f;
|
||||||
|
public static float AntennaHorizontalOffsetMultiplier { get; set; } = 0.35f;
|
||||||
|
public static float AntennaVerticalStartMultiplier { get; set; } = 0.9f;
|
||||||
|
public static float AntennaVerticalStepMultiplier { get; set; } = 0.3f;
|
||||||
|
|
||||||
|
public static void Spawn(IClientWorldAccessor world, BlockPos hivePosition, InternalBeeParticle bee)
|
||||||
|
{
|
||||||
|
var relativePosition = bee.Position;
|
||||||
|
var velocity = bee.Velocity;
|
||||||
|
|
||||||
|
var middleParticleProps = SpawnBody(world, hivePosition, relativePosition, velocity);
|
||||||
|
|
||||||
|
var accDirXZ = bee.AccelerationDirection with { Y = 0 };
|
||||||
|
Vector3 direction = bee.LookDirection.HasValue
|
||||||
|
? bee.LookDirection.Value with { Y = 0 } is var ld && ld.LengthSquared() > 0.001f ? Vector3.Normalize(ld) : Vector3.UnitZ
|
||||||
|
: accDirXZ.Length() > 0.001f ? Vector3.Normalize(accDirXZ) : Vector3.UnitZ;
|
||||||
|
double offset = middleParticleProps.MinSize / BodyOffsetDivider;
|
||||||
|
|
||||||
|
var wingPhase = bee.GetWingPhase();
|
||||||
|
|
||||||
|
SpawnHead(world, hivePosition, relativePosition, velocity, direction, offset, middleParticleProps.MinSize);
|
||||||
|
SpawnTail(world, hivePosition, relativePosition, velocity, direction, offset, middleParticleProps.MinSize);
|
||||||
|
SpawnWing(world, hivePosition, relativePosition, velocity, direction, offset, middleParticleProps.MinSize, wingPhase, WingSide.Left);
|
||||||
|
SpawnWing(world, hivePosition, relativePosition, velocity, direction, offset, middleParticleProps.MinSize, wingPhase, WingSide.Right);
|
||||||
|
if (Config.Instance.EyesAndAntennaeEnabled)
|
||||||
|
{
|
||||||
|
SpawnEyes(world, hivePosition, relativePosition, velocity, direction, offset, middleParticleProps.MinSize);
|
||||||
|
SpawnAntennae(world, hivePosition, relativePosition, velocity, direction, offset, middleParticleProps.MinSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SimpleParticleProperties SpawnBody(IClientWorldAccessor world, BlockPos hivePosition, Vector3 relativePosition, Vector3 velocity)
|
||||||
|
{
|
||||||
|
var bodyParticleProps = BaseProperties.Clone(world);
|
||||||
|
bodyParticleProps.MinPos = new Vec3d(relativePosition.X, relativePosition.Y, relativePosition.Z).Add(hivePosition);
|
||||||
|
bodyParticleProps.MinVelocity = new Vec3f(velocity.X, velocity.Y, velocity.Z);
|
||||||
|
|
||||||
|
world.SpawnParticles(bodyParticleProps);
|
||||||
|
return bodyParticleProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SpawnHead(IClientWorldAccessor world, BlockPos hivePosition, Vector3 relativePosition, Vector3 velocity, Vector3 direction, double offset, float bodySize)
|
||||||
|
{
|
||||||
|
var headParticleProps = BaseProperties.Clone(world);
|
||||||
|
headParticleProps.Color = ColorUtil.ToRgba(255, 0, 0, 0);
|
||||||
|
headParticleProps.MinPos = new Vec3d(
|
||||||
|
relativePosition.X + direction.X * offset * HeadOffsetMultiplier,
|
||||||
|
relativePosition.Y + (bodySize / BodyOffsetDivider) * HeadVerticalOffsetMultiplier,
|
||||||
|
relativePosition.Z + direction.Z * offset * HeadOffsetMultiplier
|
||||||
|
).Add(hivePosition);
|
||||||
|
headParticleProps.MinVelocity = new Vec3f(velocity.X, velocity.Y, velocity.Z);
|
||||||
|
|
||||||
|
world.SpawnParticles(headParticleProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SpawnTail(IClientWorldAccessor world, BlockPos hivePosition, Vector3 relativePosition, Vector3 velocity, Vector3 direction, double offset, float bodySize)
|
||||||
|
{
|
||||||
|
var tailParticleProps = BaseProperties.Clone(world);
|
||||||
|
tailParticleProps.Color = ColorUtil.ToRgba(255, 0, 0, 0);
|
||||||
|
tailParticleProps.MinPos = new Vec3d(
|
||||||
|
relativePosition.X - direction.X * offset * TailOffsetMultiplier,
|
||||||
|
relativePosition.Y - (bodySize / BodyOffsetDivider) * TailVerticalOffsetMultiplier,
|
||||||
|
relativePosition.Z - direction.Z * offset * TailOffsetMultiplier
|
||||||
|
).Add(hivePosition);
|
||||||
|
tailParticleProps.MinVelocity = new Vec3f(velocity.X, velocity.Y, velocity.Z);
|
||||||
|
|
||||||
|
world.SpawnParticles(tailParticleProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SpawnWing(IClientWorldAccessor world, BlockPos hivePosition, Vector3 relativePosition, Vector3 velocity, Vector3 direction, double offset, float bodySize, bool wingPhase, WingSide side)
|
||||||
|
{
|
||||||
|
var wingDirection = side == WingSide.Left
|
||||||
|
? new Vector3(-direction.Z, 0f, direction.X)
|
||||||
|
: new Vector3(direction.Z, 0f, -direction.X);
|
||||||
|
var wingParticleProps = CreateWingProps(world);
|
||||||
|
var wingForwardOffsetMultiplier = wingPhase ? WingForwardOffsetMultiplierWhenFlap : WingForwardOffsetMultiplierWhenRest;
|
||||||
|
var wingVerticalOffsetMultiplier = wingPhase ? WingVerticalOffsetMultiplierWhenFlap : WingVerticalOffsetMultiplierWhenRest;
|
||||||
|
|
||||||
|
wingParticleProps.MinPos = new Vec3d(
|
||||||
|
relativePosition.X + wingDirection.X * offset * WingHorizontalOffsetMultiplier + direction.X * offset * wingForwardOffsetMultiplier,
|
||||||
|
relativePosition.Y + (bodySize / BodyOffsetDivider) * wingVerticalOffsetMultiplier,
|
||||||
|
relativePosition.Z + wingDirection.Z * offset * WingHorizontalOffsetMultiplier + direction.Z * offset * wingForwardOffsetMultiplier
|
||||||
|
).Add(hivePosition).Add(WingPosRandomness * -0.5);
|
||||||
|
wingParticleProps.AddPos = WingPosRandomness;
|
||||||
|
wingParticleProps.MinVelocity = new Vec3f(velocity.X, velocity.Y, velocity.Z);
|
||||||
|
|
||||||
|
world.SpawnParticles(wingParticleProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SpawnEyes(IClientWorldAccessor world, BlockPos hivePosition, Vector3 relativePosition, Vector3 velocity, Vector3 direction, double offset, float bodySize)
|
||||||
|
{
|
||||||
|
var sideDirection = new Vector3(-direction.Z, 0f, direction.X);
|
||||||
|
if (sideDirection.Length() < 0.001f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
sideDirection = Vector3.Normalize(sideDirection);
|
||||||
|
var forward = direction * (float)offset * EyeForwardOffsetMultiplier;
|
||||||
|
var vertical = (bodySize / BodyOffsetDivider) * EyeVerticalOffsetMultiplier;
|
||||||
|
var horizontal = sideDirection * (float)offset * EyeHorizontalOffsetMultiplier;
|
||||||
|
|
||||||
|
var leftEye = CreateEyeProps(world);
|
||||||
|
leftEye.MinPos = new Vec3d(
|
||||||
|
relativePosition.X + forward.X + horizontal.X,
|
||||||
|
relativePosition.Y + vertical,
|
||||||
|
relativePosition.Z + forward.Z + horizontal.Z
|
||||||
|
).Add(hivePosition);
|
||||||
|
leftEye.MinVelocity = new Vec3f(velocity.X, velocity.Y, velocity.Z);
|
||||||
|
world.SpawnParticles(leftEye);
|
||||||
|
|
||||||
|
var rightEye = CreateEyeProps(world);
|
||||||
|
rightEye.MinPos = new Vec3d(
|
||||||
|
relativePosition.X + forward.X - horizontal.X,
|
||||||
|
relativePosition.Y + vertical,
|
||||||
|
relativePosition.Z + forward.Z - horizontal.Z
|
||||||
|
).Add(hivePosition);
|
||||||
|
rightEye.MinVelocity = new Vec3f(velocity.X, velocity.Y, velocity.Z);
|
||||||
|
world.SpawnParticles(rightEye);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SpawnAntennae(IClientWorldAccessor world, BlockPos hivePosition, Vector3 relativePosition, Vector3 velocity, Vector3 direction, double offset, float bodySize)
|
||||||
|
{
|
||||||
|
var sideDirection = new Vector3(-direction.Z, 0f, direction.X);
|
||||||
|
if (sideDirection.Length() < 0.001f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
sideDirection = Vector3.Normalize(sideDirection);
|
||||||
|
var forward = direction * (float)offset * AntennaForwardOffsetMultiplier;
|
||||||
|
var horizontal = sideDirection * (float)offset * AntennaHorizontalOffsetMultiplier;
|
||||||
|
var baseVertical = (bodySize / BodyOffsetDivider) * AntennaVerticalStartMultiplier;
|
||||||
|
|
||||||
|
for (var i = 0; i < AntennaSegments; i++)
|
||||||
|
{
|
||||||
|
var stepVertical = baseVertical + (bodySize / BodyOffsetDivider) * AntennaVerticalStepMultiplier * i;
|
||||||
|
var segmentProps = CreateEyeProps(world);
|
||||||
|
segmentProps.MinSize = AntennaSize;
|
||||||
|
segmentProps.MaxSize = AntennaSize;
|
||||||
|
|
||||||
|
segmentProps.MinPos = new Vec3d(
|
||||||
|
relativePosition.X + forward.X + horizontal.X,
|
||||||
|
relativePosition.Y + stepVertical,
|
||||||
|
relativePosition.Z + forward.Z + horizontal.Z
|
||||||
|
).Add(hivePosition);
|
||||||
|
segmentProps.MinVelocity = new Vec3f(velocity.X, velocity.Y, velocity.Z);
|
||||||
|
world.SpawnParticles(segmentProps);
|
||||||
|
|
||||||
|
var mirroredSegmentProps = segmentProps.Clone(world);
|
||||||
|
mirroredSegmentProps.MinPos = new Vec3d(
|
||||||
|
relativePosition.X + forward.X - horizontal.X,
|
||||||
|
relativePosition.Y + stepVertical,
|
||||||
|
relativePosition.Z + forward.Z - horizontal.Z
|
||||||
|
).Add(hivePosition);
|
||||||
|
mirroredSegmentProps.MinVelocity = new Vec3f(velocity.X, velocity.Y, velocity.Z);
|
||||||
|
world.SpawnParticles(mirroredSegmentProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SimpleParticleProperties CreateWingProps(IClientWorldAccessor world)
|
||||||
|
{
|
||||||
|
var wingParticleProps = BaseProperties.Clone(world);
|
||||||
|
wingParticleProps.Color = ColorUtil.ToRgba(50, 255, 255, 100);
|
||||||
|
wingParticleProps.ParticleModel = EnumParticleModel.Cube;
|
||||||
|
wingParticleProps.MinSize = WingSize;
|
||||||
|
wingParticleProps.MaxSize = WingSize;
|
||||||
|
|
||||||
|
return wingParticleProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SimpleParticleProperties CreateEyeProps(IClientWorldAccessor world)
|
||||||
|
{
|
||||||
|
var eyeParticleProps = BaseProperties.Clone(world);
|
||||||
|
eyeParticleProps.Color = ColorUtil.ToRgba(255, 51, 51, 51);
|
||||||
|
eyeParticleProps.ParticleModel = EnumParticleModel.Cube;
|
||||||
|
eyeParticleProps.MinSize = EyeSize;
|
||||||
|
eyeParticleProps.MaxSize = EyeSize;
|
||||||
|
return eyeParticleProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum WingSide
|
||||||
|
{
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using ProtoBuf;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace RoamingBees.Particles.Catchup;
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public record struct BeeCatchupPacket
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public required BlockPos HivePosition;
|
||||||
|
|
||||||
|
[ProtoMember(2)]
|
||||||
|
public required IEnumerable<BeeSpawnPacket> SpawnPackets;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
using ProtoBuf;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace RoamingBees.Particles.Catchup;
|
||||||
|
|
||||||
|
[ProtoContract]
|
||||||
|
public record struct BeeCatchupRequestPacket
|
||||||
|
{
|
||||||
|
[ProtoMember(1)]
|
||||||
|
public required BlockPos HivePosition;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace RoamingBees.Particles.Catchup;
|
||||||
|
|
||||||
|
public interface IBeeSpawnCatchup
|
||||||
|
{
|
||||||
|
IEnumerable<BeeSpawnPacket> ActiveBeesPackets { get; }
|
||||||
|
}
|
||||||
17
RoamingBees/RoamingBees/Particles/Catchup/ParticleCatchup.cs
Normal file
17
RoamingBees/RoamingBees/Particles/Catchup/ParticleCatchup.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
using Vintagestory.API.Client;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.Server;
|
||||||
|
|
||||||
|
namespace RoamingBees.Particles.Catchup;
|
||||||
|
|
||||||
|
public class ParticleCatchupManager(IBeeSpawnCatchup origin, ICoreAPI api)
|
||||||
|
{
|
||||||
|
public IBeeSpawnCatchup Origin { get; } = origin;
|
||||||
|
public ICoreServerAPI? ServerApi { get; } = api as ICoreServerAPI;
|
||||||
|
public ICoreClientAPI? ClientApi { get; } = api as ICoreClientAPI;
|
||||||
|
|
||||||
|
public void SendCatchup()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
92
RoamingBees/RoamingBees/Particles/DebugPathBoxRenderer.cs
Normal file
92
RoamingBees/RoamingBees/Particles/DebugPathBoxRenderer.cs
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
using System;
|
||||||
|
using Vintagestory.API.Client;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace RoamingBees.Particles;
|
||||||
|
|
||||||
|
internal static class DebugPathBoxRenderer
|
||||||
|
{
|
||||||
|
private const double edge_step = 0.04;
|
||||||
|
private static SimpleParticleProperties? _markerProperties;
|
||||||
|
|
||||||
|
private static SimpleParticleProperties MarkerProperties()
|
||||||
|
{
|
||||||
|
return _markerProperties ??= new SimpleParticleProperties()
|
||||||
|
{
|
||||||
|
MinPos = new Vec3d(),
|
||||||
|
MinVelocity = new Vec3f(),
|
||||||
|
MinSize = 0.16f,
|
||||||
|
MaxSize = 0.16f,
|
||||||
|
LifeLength = 0.45f,
|
||||||
|
WithTerrainCollision = false,
|
||||||
|
ParticleModel = EnumParticleModel.Cube,
|
||||||
|
MinQuantity = 1,
|
||||||
|
AddQuantity = 0,
|
||||||
|
Color = ColorUtil.ToRgba(235, 40, 255, 255),
|
||||||
|
GravityEffect = 0f,
|
||||||
|
SelfPropelled = false,
|
||||||
|
RandomVelocityChange = false,
|
||||||
|
WindAffected = false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Render(IClientWorldAccessor world, Cuboidf box)
|
||||||
|
{
|
||||||
|
var p000 = new Vec3d(box.X1, box.Y1, box.Z1);
|
||||||
|
var p100 = new Vec3d(box.X2, box.Y1, box.Z1);
|
||||||
|
var p010 = new Vec3d(box.X1, box.Y2, box.Z1);
|
||||||
|
var p110 = new Vec3d(box.X2, box.Y2, box.Z1);
|
||||||
|
var p001 = new Vec3d(box.X1, box.Y1, box.Z2);
|
||||||
|
var p101 = new Vec3d(box.X2, box.Y1, box.Z2);
|
||||||
|
var p011 = new Vec3d(box.X1, box.Y2, box.Z2);
|
||||||
|
var p111 = new Vec3d(box.X2, box.Y2, box.Z2);
|
||||||
|
|
||||||
|
SpawnEdge(world, p000, p100);
|
||||||
|
SpawnEdge(world, p000, p010);
|
||||||
|
SpawnEdge(world, p000, p001);
|
||||||
|
|
||||||
|
SpawnEdge(world, p111, p011);
|
||||||
|
SpawnEdge(world, p111, p101);
|
||||||
|
SpawnEdge(world, p111, p110);
|
||||||
|
|
||||||
|
SpawnEdge(world, p100, p101);
|
||||||
|
SpawnEdge(world, p100, p110);
|
||||||
|
|
||||||
|
SpawnEdge(world, p010, p011);
|
||||||
|
SpawnEdge(world, p010, p110);
|
||||||
|
|
||||||
|
SpawnEdge(world, p001, p011);
|
||||||
|
SpawnEdge(world, p001, p101);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SpawnEdge(IClientWorldAccessor world, Vec3d from, Vec3d to)
|
||||||
|
{
|
||||||
|
var direction = to - from;
|
||||||
|
var length = direction.Length();
|
||||||
|
if (length <= 0)
|
||||||
|
{
|
||||||
|
SpawnPoint(world, from.X, from.Y, from.Z);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var steps = Math.Max(1, (int)Math.Ceiling(length / edge_step));
|
||||||
|
for (var i = 0; i <= steps; i++)
|
||||||
|
{
|
||||||
|
var t = (double)i / steps;
|
||||||
|
SpawnPoint(
|
||||||
|
world,
|
||||||
|
from.X + direction.X * t,
|
||||||
|
from.Y + direction.Y * t,
|
||||||
|
from.Z + direction.Z * t
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SpawnPoint(IClientWorldAccessor world, double x, double y, double z)
|
||||||
|
{
|
||||||
|
var props = MarkerProperties().Clone(world);
|
||||||
|
props.MinPos = new Vec3d(x, y, z);
|
||||||
|
world.SpawnParticles(props);
|
||||||
|
}
|
||||||
|
}
|
||||||
65
RoamingBees/RoamingBees/Particles/DebugPathRenderManager.cs
Normal file
65
RoamingBees/RoamingBees/Particles/DebugPathRenderManager.cs
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
using Vintagestory.API.Client;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace RoamingBees.Particles;
|
||||||
|
|
||||||
|
internal static class DebugPathRenderManager
|
||||||
|
{
|
||||||
|
private const int debug_paths_radius = 5;
|
||||||
|
private const int debug_paths_update_interval_ms = 100;
|
||||||
|
|
||||||
|
private static ICoreClientAPI? clientApi;
|
||||||
|
private static long? tickListenerId;
|
||||||
|
|
||||||
|
public static void SetEnabled(ICoreClientAPI? api, bool enabled)
|
||||||
|
{
|
||||||
|
if (clientApi != api)
|
||||||
|
{
|
||||||
|
DisableCurrent();
|
||||||
|
clientApi = api;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientApi == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
if (!tickListenerId.HasValue)
|
||||||
|
tickListenerId = clientApi.Event.RegisterGameTickListener(OnTick, debug_paths_update_interval_ms);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DisableCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DisableCurrent()
|
||||||
|
{
|
||||||
|
if (clientApi != null && tickListenerId.HasValue)
|
||||||
|
clientApi.Event.UnregisterGameTickListener(tickListenerId.Value);
|
||||||
|
tickListenerId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnTick(float _)
|
||||||
|
{
|
||||||
|
if (clientApi?.World is not IClientWorldAccessor clientWorld)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var playerEntity = clientWorld.Player?.Entity;
|
||||||
|
if (playerEntity == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var playerPos = playerEntity.Pos.AsBlockPos;
|
||||||
|
var blockAccessor = clientWorld.BlockAccessor;
|
||||||
|
|
||||||
|
for (var dx = -debug_paths_radius; dx <= debug_paths_radius; dx++)
|
||||||
|
for (var dy = -debug_paths_radius; dy <= debug_paths_radius; dy++)
|
||||||
|
for (var dz = -debug_paths_radius; dz <= debug_paths_radius; dz++)
|
||||||
|
{
|
||||||
|
var blockPos = new BlockPos(playerPos.X + dx, playerPos.Y + dy, playerPos.Z + dz);
|
||||||
|
if (!BeePathGeneration.TryGetTargetBoxAtWorldBlock(blockAccessor, blockPos, out var box))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
DebugPathBoxRenderer.Render(clientWorld, box);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
RoamingBees/RoamingBees/Particles/IBeeSpawnHandler.cs
Normal file
6
RoamingBees/RoamingBees/Particles/IBeeSpawnHandler.cs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
namespace RoamingBees.Particles;
|
||||||
|
|
||||||
|
public interface IBeeSpawnHandler
|
||||||
|
{
|
||||||
|
void HandleBeeParticleSpawn(BeeSpawnPacket packet, bool catchup = false);
|
||||||
|
}
|
||||||
283
RoamingBees/RoamingBees/Particles/InternalBeeParticle.cs
Normal file
283
RoamingBees/RoamingBees/Particles/InternalBeeParticle.cs
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Numerics;
|
||||||
|
|
||||||
|
namespace RoamingBees.Particles;
|
||||||
|
|
||||||
|
public class InternalBeeParticle(Vector3 startingPosition, Vector3 frontDirection, BeePlannedPathPoint[] path, BeeRole role, Vector3 despawnPosition, BeeSpawnPacket spawnPacket)
|
||||||
|
{
|
||||||
|
private readonly BeePlannedPathPoint[] path = path;
|
||||||
|
|
||||||
|
private float totalDt = 0;
|
||||||
|
private int currentTargetIndex = 0;
|
||||||
|
|
||||||
|
public float MaxSpeedTraveling { get; } = 2.0f;
|
||||||
|
public float TargetSpeedApproaching { get; } = 0.3f;
|
||||||
|
public float TargetSpeedHovering { get; } = 0.1f;
|
||||||
|
public float TargetSpeedReturning { get; } = 0.02f;
|
||||||
|
|
||||||
|
public float SpeedChangeTimeSeconds { get; } = 1f;
|
||||||
|
|
||||||
|
public float IntermediateWaypointDistanceThreshold { get; } = 0.6f;
|
||||||
|
public float ApproachingDistanceThreshold { get; } = 2.5f;
|
||||||
|
public float HoverDistanceThreshold { get; } = 0.3f;
|
||||||
|
|
||||||
|
public Vector3 StartingPosition { get; } = startingPosition;
|
||||||
|
public Vector3 DespawnPosition { get; } = despawnPosition;
|
||||||
|
public BeeSpawnPacket SpawnPacket { get; } = spawnPacket;
|
||||||
|
|
||||||
|
public Vector3 Position { get; private set; } = startingPosition;
|
||||||
|
public Vector3 Velocity { get; private set; } = frontDirection.Length() > 0.01f ? (Vector3.Normalize(frontDirection) * 0.3f + Vector3.UnitY * 0.1f) : Vector3.Zero;
|
||||||
|
public Vector3 AccelerationDirection { get; private set; }
|
||||||
|
|
||||||
|
/// <summary> from 0 to 1 </summary>
|
||||||
|
public float AccelerationMagnitude { get; private set; }
|
||||||
|
|
||||||
|
public BeePlannedPathPoint? Target => path.ElementAtOrDefault(currentTargetIndex);
|
||||||
|
public Vector3? NextTarget { get; private set; }
|
||||||
|
public float TimeAtTarget { get; private set; }
|
||||||
|
public float DesiredTimeAtTarget { get; } = 4f;
|
||||||
|
|
||||||
|
public bool ShouldBeDespawned { get; private set; }
|
||||||
|
|
||||||
|
public int Ticks { get; private set; }
|
||||||
|
|
||||||
|
public BeeRole Role { get; } = role;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// unset = use AccelerationDirection
|
||||||
|
/// </summary>
|
||||||
|
public Vector3? LookDirection { get; set; } = path.ElementAtOrDefault(0).LookDirection;
|
||||||
|
|
||||||
|
public void SolveCollision(Vector3 min, Vector3 max, Vector3 outDir)
|
||||||
|
{
|
||||||
|
var p = Position;
|
||||||
|
|
||||||
|
// push along the outward axis to the matching face
|
||||||
|
Position = outDir switch
|
||||||
|
{
|
||||||
|
{ X: > 0.5f } => p with { X = max.X + 0.01f },
|
||||||
|
{ X: < -0.5f } => p with { X = min.X - 0.01f },
|
||||||
|
{ Y: > 0.5f } => p with { Y = max.Y + 0.01f },
|
||||||
|
{ Y: < -0.5f } => p with { Y = min.Y - 0.01f },
|
||||||
|
{ Z: > 0.5f } => p with { Z = max.Z + 0.01f },
|
||||||
|
_ => p with { Z = min.Z - 0.01f },
|
||||||
|
};
|
||||||
|
|
||||||
|
// kill velocity
|
||||||
|
var vDotOut = Vector3.Dot(Velocity, outDir);
|
||||||
|
if (vDotOut < 0f)
|
||||||
|
Velocity -= outDir * vDotOut;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Step(float dt, float windSpeed)
|
||||||
|
{
|
||||||
|
Ticks++;
|
||||||
|
totalDt += dt;
|
||||||
|
|
||||||
|
|
||||||
|
SetAccelerationTowardsTarget();
|
||||||
|
if (AccelerationDirection.Length() > 0.1f)
|
||||||
|
Velocity += Vector3.Normalize(AccelerationDirection) * dt * Math.Clamp(AccelerationMagnitude, 0f, 2f);
|
||||||
|
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
var windNoiseStrength = cfg.BaseWindNoiseStrength + (windSpeed * (cfg.MaxWindNoiseStrength - cfg.BaseWindNoiseStrength));
|
||||||
|
var noise = GetAccelerationNoise(windSpeed);
|
||||||
|
if (noise.Length() > 0.01f)
|
||||||
|
Velocity += Vector3.Normalize(noise) * windNoiseStrength * dt;
|
||||||
|
|
||||||
|
if (Velocity.Length() > MaxSpeedTraveling)
|
||||||
|
Velocity = Vector3.Normalize(Velocity) * MaxSpeedTraveling;
|
||||||
|
|
||||||
|
Position += Velocity * dt;
|
||||||
|
|
||||||
|
if (Target is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
|
||||||
|
var state = GetTravelState();
|
||||||
|
|
||||||
|
if (state is TravelState.IntermediateWaypoint)
|
||||||
|
{
|
||||||
|
currentTargetIndex++;
|
||||||
|
LookDirection = Target?.LookDirection;
|
||||||
|
TimeAtTarget = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state is TravelState.Hovering)
|
||||||
|
{
|
||||||
|
// here we have to decrease Velocity over SpeedChangeTimeSeconds onto the appropriate speed
|
||||||
|
TimeAtTarget += dt;
|
||||||
|
// decrease speed gradually from approach speed to "at target" speed over SpeedChangeTimeSeconds
|
||||||
|
var needsBraking = IsMovingAwayOrSideways();
|
||||||
|
if (!Target.Value.IsSlowdown)
|
||||||
|
{
|
||||||
|
var hoverSpeed = Role == BeeRole.Hovering ? 0.06f : TargetSpeedHovering;
|
||||||
|
var brakeDt = Role == BeeRole.Hovering ? dt * 2f : dt;
|
||||||
|
DecreaseVelocityTowardsSpeed(needsBraking ? 0f : hoverSpeed, brakeDt);
|
||||||
|
HandleBeingAtPlantTarget();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DecreaseVelocityTowardsSpeed(needsBraking ? 0 : TargetSpeedHovering, dt * 2);
|
||||||
|
HandleEndOfJourney();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (state is TravelState.Approaching)
|
||||||
|
{
|
||||||
|
// same shit but from traveling speed to approaching speed
|
||||||
|
var needsBraking = IsMovingAwayOrSideways();
|
||||||
|
var targetSpeed = Target.Value.IsSlowdown ? TargetSpeedReturning : TargetSpeedApproaching;
|
||||||
|
DecreaseVelocityTowardsSpeed(needsBraking ? 0 : targetSpeed, dt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool GetWingPhase()
|
||||||
|
{
|
||||||
|
var quarterSeconds = (int)(totalDt * 16);
|
||||||
|
return quarterSeconds % 2 == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleEndOfJourney()
|
||||||
|
{
|
||||||
|
var distanceToTarget = Vector3.Distance(Target!.Value.Position, Position);
|
||||||
|
if (Target.Value.Position != DespawnPosition && distanceToTarget < 0.1f && TimeAtTarget > 1)
|
||||||
|
{
|
||||||
|
currentTargetIndex++;
|
||||||
|
LookDirection = Target?.LookDirection;
|
||||||
|
TimeAtTarget = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Target!.Value.Position == DespawnPosition && TimeAtTarget > 1f && Vector3.Distance(DespawnPosition, Position) < 0.1f)
|
||||||
|
ShouldBeDespawned = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleBeingAtPlantTarget()
|
||||||
|
{
|
||||||
|
if (TimeAtTarget <= DesiredTimeAtTarget || Target!.Value.Position == DespawnPosition)
|
||||||
|
return;
|
||||||
|
|
||||||
|
TimeAtTarget = 0;
|
||||||
|
currentTargetIndex++;
|
||||||
|
LookDirection = Target?.LookDirection;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DecreaseVelocityTowardsSpeed(float toSpeed, float dt)
|
||||||
|
{
|
||||||
|
var currentSpeed = Velocity.Length();
|
||||||
|
if (currentSpeed < 0.01f)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float lerpFactor = Math.Clamp(dt * 2f, 0f, 1f);
|
||||||
|
float newSpeed = currentSpeed + (toSpeed - currentSpeed) * lerpFactor;
|
||||||
|
|
||||||
|
Velocity = Vector3.Normalize(Velocity) * newSpeed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool IsMovingAwayOrSideways()
|
||||||
|
{
|
||||||
|
if (Target == null || Velocity.Length() < 0.01f)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var directionToTarget = Target.Value.Position - Position;
|
||||||
|
if (directionToTarget.Length() < 0.01f)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var velocityNorm = Vector3.Normalize(Velocity);
|
||||||
|
var targetDirNorm = Vector3.Normalize(directionToTarget);
|
||||||
|
float alignment = Vector3.Dot(velocityNorm, targetDirNorm);
|
||||||
|
|
||||||
|
if (Target.Value.IsSlowdown)
|
||||||
|
return alignment < 0.97f;
|
||||||
|
|
||||||
|
return alignment < 0.6f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetAccelerationTowardsTarget()
|
||||||
|
{
|
||||||
|
var distanceToEntrance = Vector3.Distance(StartingPosition, Position);
|
||||||
|
if (Target == null || (totalDt <= 1 && distanceToEntrance <= 0.1f))
|
||||||
|
{
|
||||||
|
if (Velocity.LengthSquared() > 0.1)
|
||||||
|
{
|
||||||
|
AccelerationDirection = Vector3.Normalize(Velocity);
|
||||||
|
AccelerationMagnitude = 0.01f;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var directionToTarget = Target.Value.Position - Position;
|
||||||
|
var distance = directionToTarget.Length();
|
||||||
|
|
||||||
|
if (distance > 0.01f)
|
||||||
|
{
|
||||||
|
directionToTarget = Vector3.Normalize(directionToTarget);
|
||||||
|
AccelerationDirection = directionToTarget;
|
||||||
|
AccelerationMagnitude = 0.7f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AccelerationDirection = Vector3.Zero;
|
||||||
|
AccelerationMagnitude = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Target.Value.IsSlowdown)
|
||||||
|
{
|
||||||
|
AccelerationMagnitude = 1f;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var state = GetTravelState();
|
||||||
|
if (state is TravelState.Approaching)
|
||||||
|
AccelerationMagnitude = 0.3f;
|
||||||
|
|
||||||
|
if (state is TravelState.Hovering)
|
||||||
|
AccelerationMagnitude = Role == BeeRole.Hovering ? 0.2f : 0.1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Vector3 GetAccelerationNoise(float windSpeed)
|
||||||
|
{
|
||||||
|
if ((GetTravelState() is TravelState.Approaching or TravelState.Hovering) && Target?.IsSlowdown == true)
|
||||||
|
return Vector3.Zero;
|
||||||
|
|
||||||
|
// hovering-role bees hug the swarm - suppress noise so they don't sway off
|
||||||
|
if (Role == BeeRole.Hovering && GetTravelState() is TravelState.Hovering or TravelState.Approaching)
|
||||||
|
return Vector3.Zero;
|
||||||
|
|
||||||
|
var cfg = Config.Instance;
|
||||||
|
float windFrequency = cfg.BaseWindFrequency + (windSpeed * cfg.BaseWindFrequency * cfg.WindFrequencyMultiplier);
|
||||||
|
float t = Ticks * windFrequency;
|
||||||
|
|
||||||
|
float x = MathF.Sin(t);
|
||||||
|
float y = MathF.Sin(t * 1.3f + 2.1f);
|
||||||
|
float z = MathF.Sin(t * 0.7f + 4.2f);
|
||||||
|
|
||||||
|
return new Vector3(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
private TravelState GetTravelState()
|
||||||
|
{
|
||||||
|
if (Target is null)
|
||||||
|
return TravelState.Idle;
|
||||||
|
|
||||||
|
var distance = Vector3.Distance(Position, Target.Value.Position);
|
||||||
|
if (Target.Value.IsIntermediate && distance < IntermediateWaypointDistanceThreshold)
|
||||||
|
return TravelState.IntermediateWaypoint;
|
||||||
|
if (distance < HoverDistanceThreshold)
|
||||||
|
return TravelState.Hovering;
|
||||||
|
if (distance < ApproachingDistanceThreshold)
|
||||||
|
return TravelState.Approaching;
|
||||||
|
return TravelState.FastTraveling;
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum TravelState
|
||||||
|
{
|
||||||
|
FastTraveling,
|
||||||
|
Approaching,
|
||||||
|
Hovering,
|
||||||
|
IntermediateWaypoint,
|
||||||
|
Idle,
|
||||||
|
}
|
||||||
|
}
|
||||||
272
RoamingBees/RoamingBees/Particles/SwarmBeePathGeneration.cs
Normal file
272
RoamingBees/RoamingBees/Particles/SwarmBeePathGeneration.cs
Normal file
@@ -0,0 +1,272 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using Vintagestory.API.Common;
|
||||||
|
using Vintagestory.API.MathTools;
|
||||||
|
|
||||||
|
namespace RoamingBees.Particles;
|
||||||
|
|
||||||
|
public static class SwarmBeePathGeneration
|
||||||
|
{
|
||||||
|
private const bool force_path_even_when_blocked = false;
|
||||||
|
private static Random random { get; } = new();
|
||||||
|
private static List<BeePlannedPathPoint> temp_list { get; } = [];
|
||||||
|
|
||||||
|
public static Vector3 ComputeHiddenAnchor(Vec3f[] hoverSurfaceMap, string swarmSide)
|
||||||
|
{
|
||||||
|
var centroid = GetCentroid(hoverSurfaceMap);
|
||||||
|
|
||||||
|
return swarmSide switch
|
||||||
|
{
|
||||||
|
"north" => centroid with { Z = 0f },
|
||||||
|
"south" => centroid with { Z = 1f },
|
||||||
|
"east" => centroid with { X = 1f },
|
||||||
|
"west" => centroid with { X = 0f },
|
||||||
|
"down" => centroid with { Y = 0f },
|
||||||
|
"up" => centroid with { Y = 1f },
|
||||||
|
_ => centroid with { Z = 0f },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BeePlannedPathPoint[] GenerateBuildingPath(
|
||||||
|
IBlockAccessor blockAccessor,
|
||||||
|
BlockPos swarmPos,
|
||||||
|
Vector3 originEntranceRelative,
|
||||||
|
Vector3 originFrontDir,
|
||||||
|
Vector3 hiddenAnchor)
|
||||||
|
{
|
||||||
|
temp_list.Clear();
|
||||||
|
|
||||||
|
// spawn at the origin hive entrance, first waypoint exits through the front
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(originEntranceRelative, true, false));
|
||||||
|
var exitPoint = BeePathGeneration.GetFrontOfEntrance(originEntranceRelative, originFrontDir);
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(exitPoint, false, false));
|
||||||
|
|
||||||
|
// random approach point near the swarm block center for variety
|
||||||
|
var approach = new Vector3(0.5f, 0.5f, 0.5f) + new Vector3(
|
||||||
|
(float)(random.NextDouble() * 0.6 - 0.3),
|
||||||
|
(float)(random.NextDouble() * 0.6 - 0.3),
|
||||||
|
(float)(random.NextDouble() * 0.6 - 0.3));
|
||||||
|
if (!AddLineOfSightSegment(blockAccessor, swarmPos, exitPoint, approach))
|
||||||
|
return [];
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(approach, false, false));
|
||||||
|
|
||||||
|
if (!AddLineOfSightSegment(blockAccessor, swarmPos, approach, hiddenAnchor))
|
||||||
|
return [];
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(hiddenAnchor, false, true));
|
||||||
|
|
||||||
|
return [.. temp_list];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BeePlannedPathPoint[] GenerateHoverPath(
|
||||||
|
IBlockAccessor blockAccessor,
|
||||||
|
BlockPos swarmPos,
|
||||||
|
Vec3f[] hoverSurfaceMap,
|
||||||
|
Vector3 hiddenAnchor,
|
||||||
|
string swarmSide)
|
||||||
|
{
|
||||||
|
if (hoverSurfaceMap.Length == 0)
|
||||||
|
return [];
|
||||||
|
|
||||||
|
temp_list.Clear();
|
||||||
|
|
||||||
|
var outDir = swarmSide switch
|
||||||
|
{
|
||||||
|
"north" => new Vector3(0f, 0f, 1f),
|
||||||
|
"south" => new Vector3(0f, 0f, -1f),
|
||||||
|
"east" => new Vector3(-1f, 0f, 0f),
|
||||||
|
"west" => new Vector3( 1f, 0f, 0f),
|
||||||
|
"up" => new Vector3(0f, 1f, 0f),
|
||||||
|
"down" => new Vector3(0f, -1f, 0f),
|
||||||
|
_ => new Vector3(0f, 0f, 1f),
|
||||||
|
};
|
||||||
|
var up = Math.Abs(outDir.Y) < 0.99f ? Vector3.UnitY : Vector3.UnitX;
|
||||||
|
var tangent = Vector3.Normalize(Vector3.Cross(outDir, up));
|
||||||
|
|
||||||
|
if (random.NextDouble() < 0.5)
|
||||||
|
{
|
||||||
|
// tight: shape-based points only
|
||||||
|
var shapeCount = Math.Min(10, hoverSurfaceMap.Length);
|
||||||
|
for (var i = 0; i < shapeCount; i++)
|
||||||
|
{
|
||||||
|
var sample = hoverSurfaceMap[random.Next(hoverSurfaceMap.Length)];
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(new Vector3(sample.X, sample.Y, sample.Z), false, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// loose: outward points in front of the swarm face
|
||||||
|
var outwardCount = random.Next(2, 4);
|
||||||
|
var faceCenter = hiddenAnchor with { };
|
||||||
|
for (var i = 0; i < outwardCount; i++)
|
||||||
|
{
|
||||||
|
var dist = (float)(random.NextDouble() * 0.4 + 0.1);
|
||||||
|
var lateral = (float)(random.NextDouble() * 0.27 - 0.13);
|
||||||
|
var vertical = Math.Abs(outDir.Y) < 0.99f ? (float)(random.NextDouble() * 0.2 - 0.1) : 0f;
|
||||||
|
var outPos = faceCenter + outDir * dist + tangent * lateral + Vector3.UnitY * vertical;
|
||||||
|
outPos = NudgeOutOfSolid(blockAccessor, swarmPos, outPos) ?? faceCenter;
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(outPos, false, false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// shuffle all waypoints for variety, then append anchor once at end
|
||||||
|
for (var i = temp_list.Count - 1; i > 0; i--)
|
||||||
|
{
|
||||||
|
var j = random.Next(i + 1);
|
||||||
|
(temp_list[i], temp_list[j]) = (temp_list[j], temp_list[i]);
|
||||||
|
}
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(hiddenAnchor, false, true));
|
||||||
|
|
||||||
|
return [.. temp_list];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BeePlannedPathPoint[] GenerateScoutPath(
|
||||||
|
IBlockAccessor blockAccessor,
|
||||||
|
BlockPos swarmPos,
|
||||||
|
Vector3 targetEntranceRelative,
|
||||||
|
Vector3 targetFrontDir,
|
||||||
|
Vector3 hiddenAnchor)
|
||||||
|
{
|
||||||
|
temp_list.Clear();
|
||||||
|
|
||||||
|
// targetFrontDir is the outward direction from the candidate hive entrance.
|
||||||
|
var front = Vector3.Normalize(targetFrontDir);
|
||||||
|
|
||||||
|
// far approach point: bee must clear the hive block before moving in close
|
||||||
|
var farApproach = targetEntranceRelative + front * 1f;
|
||||||
|
farApproach = NudgeOutOfSolid(blockAccessor, swarmPos, farApproach) ?? farApproach;
|
||||||
|
if (!AddLineOfSightSegment(blockAccessor, swarmPos, hiddenAnchor, farApproach))
|
||||||
|
return [];
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(farApproach, true, false));
|
||||||
|
|
||||||
|
var entryPoint = targetEntranceRelative + front * 0.3f;
|
||||||
|
entryPoint = NudgeOutOfSolid(blockAccessor, swarmPos, entryPoint) ?? entryPoint;
|
||||||
|
var lookInward = -front;
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(entryPoint, false, false, lookInward));
|
||||||
|
|
||||||
|
var hoverCount = random.Next(2, 4);
|
||||||
|
var lastHoverPos = entryPoint;
|
||||||
|
for (var i = 0; i < hoverCount; i++)
|
||||||
|
{
|
||||||
|
var outward = (float)(random.NextDouble() * 0.6);
|
||||||
|
var vertical = (float)(random.NextDouble() * 0.4 - 0.2);
|
||||||
|
|
||||||
|
var angle = (float)(random.NextDouble() * MathF.PI * 2);
|
||||||
|
var up = Math.Abs(front.Y) < 0.99f ? Vector3.UnitY : Vector3.UnitX;
|
||||||
|
var tangent = Vector3.Normalize(Vector3.Cross(front, up));
|
||||||
|
var bitangent = Vector3.Cross(front, tangent);
|
||||||
|
var lateralOffset = tangent * MathF.Cos(angle) * 0.4f + bitangent * MathF.Sin(angle) * 0.4f;
|
||||||
|
|
||||||
|
var hoverPos = entryPoint + front * outward + lateralOffset + Vector3.UnitY * vertical;
|
||||||
|
hoverPos = NudgeOutOfSolid(blockAccessor, swarmPos, hoverPos) ?? entryPoint;
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(hoverPos, false, false, lookInward));
|
||||||
|
lastHoverPos = hoverPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!AddLineOfSightSegment(blockAccessor, swarmPos, lastHoverPos, hiddenAnchor))
|
||||||
|
return [];
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(hiddenAnchor, false, true));
|
||||||
|
|
||||||
|
return [.. temp_list];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BeePlannedPathPoint[] GenerateMigrationPath(
|
||||||
|
IBlockAccessor blockAccessor,
|
||||||
|
BlockPos swarmPos,
|
||||||
|
Vector3 targetEntranceRelative,
|
||||||
|
Vector3 targetFrontDir,
|
||||||
|
Vector3 hiddenAnchor)
|
||||||
|
{
|
||||||
|
temp_list.Clear();
|
||||||
|
|
||||||
|
// start at hidden anchor, consumed immediately as intermediate
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(hiddenAnchor, true, false));
|
||||||
|
|
||||||
|
// random drift midpoint for variety
|
||||||
|
var mid = new Vector3(0.5f, 0.5f, 0.5f) + new Vector3(
|
||||||
|
(float)(random.NextDouble() * 0.8 - 0.4),
|
||||||
|
(float)(random.NextDouble() * 0.6 - 0.3),
|
||||||
|
(float)(random.NextDouble() * 0.8 - 0.4));
|
||||||
|
if (!AddLineOfSightSegment(blockAccessor, swarmPos, hiddenAnchor, mid))
|
||||||
|
return [];
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(mid, true, false));
|
||||||
|
|
||||||
|
// slow approach to front of entrance, then enter
|
||||||
|
var targetFront = BeePathGeneration.GetFrontOfEntrance(targetEntranceRelative, targetFrontDir);
|
||||||
|
if (!AddLineOfSightSegment(blockAccessor, swarmPos, mid, targetFront))
|
||||||
|
return [];
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(targetFront, true, true));
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(targetEntranceRelative, false, true));
|
||||||
|
|
||||||
|
return [.. temp_list];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Vector3 GetCentroid(Vec3f[] points)
|
||||||
|
{
|
||||||
|
if (points.Length == 0)
|
||||||
|
return new Vector3(0.5f, 0.5f, 0.5f);
|
||||||
|
|
||||||
|
var sum = Vector3.Zero;
|
||||||
|
foreach (var p in points)
|
||||||
|
sum += new Vector3(p.X, p.Y, p.Z);
|
||||||
|
|
||||||
|
return sum / points.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If <paramref name="relPos"/> falls inside a solid block, tries nudging in Y+, Y-, X+, X-, Z+, Z-
|
||||||
|
/// in steps of 0.25 up to 1 block. Returns the first non-solid position found, or null if all fail.
|
||||||
|
/// </summary>
|
||||||
|
private static Vector3? NudgeOutOfSolid(IBlockAccessor blockAccessor, BlockPos swarmPos, Vector3 relPos)
|
||||||
|
{
|
||||||
|
if (!IsSolidAt(blockAccessor, swarmPos, relPos))
|
||||||
|
return relPos;
|
||||||
|
|
||||||
|
Span<Vector3> directions = [
|
||||||
|
Vector3.UnitY, -Vector3.UnitY,
|
||||||
|
Vector3.UnitX, -Vector3.UnitX,
|
||||||
|
Vector3.UnitZ, -Vector3.UnitZ,
|
||||||
|
];
|
||||||
|
|
||||||
|
for (var step = 1; step <= 4; step++)
|
||||||
|
{
|
||||||
|
var nudge = step * 0.25f;
|
||||||
|
foreach (var dir in directions)
|
||||||
|
{
|
||||||
|
var candidate = relPos + dir * nudge;
|
||||||
|
if (!IsSolidAt(blockAccessor, swarmPos, candidate))
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsSolidAt(IBlockAccessor blockAccessor, BlockPos swarmPos, Vector3 relPos)
|
||||||
|
{
|
||||||
|
var worldPos = new BlockPos(
|
||||||
|
swarmPos.X + (int)MathF.Floor(relPos.X),
|
||||||
|
swarmPos.Y + (int)MathF.Floor(relPos.Y),
|
||||||
|
swarmPos.Z + (int)MathF.Floor(relPos.Z));
|
||||||
|
var block = blockAccessor.GetBlock(worldPos);
|
||||||
|
return block is not null && block.BlockMaterial != EnumBlockMaterial.Air && block.Replaceable < 6000;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool AddLineOfSightSegment(
|
||||||
|
IBlockAccessor blockAccessor,
|
||||||
|
BlockPos anchor,
|
||||||
|
Vector3 from,
|
||||||
|
Vector3 to)
|
||||||
|
{
|
||||||
|
if (BeePathGeneration.HasLineOfSight(blockAccessor, anchor, from, to))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (BeePathGeneration.TryFindWaypoint(blockAccessor, anchor, from, to, out var waypoint))
|
||||||
|
{
|
||||||
|
temp_list.Add(new BeePlannedPathPoint(waypoint, true, false));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return force_path_even_when_blocked;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
RoamingBees/RoamingBees/Properties/launchSettings.json
Normal file
16
RoamingBees/RoamingBees/Properties/launchSettings.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"profiles": {
|
||||||
|
"Client": {
|
||||||
|
"commandName": "Executable",
|
||||||
|
"executablePath": "dotnet",
|
||||||
|
"commandLineArgs": "\"$(VINTAGE_STORY)/Vintagestory.dll\" --tracelog --addModPath \"$(ProjectDir)/bin/$(Configuration)/Mods\" --addOrigin \"$(ProjectDir)/assets\"$(VintageStoryDataPathArgs)",
|
||||||
|
"workingDirectory": "$(VINTAGE_STORY)"
|
||||||
|
},
|
||||||
|
"Server": {
|
||||||
|
"commandName": "Executable",
|
||||||
|
"executablePath": "dotnet",
|
||||||
|
"commandLineArgs": "\"$(VINTAGE_STORY)/VintagestoryServer.dll\" --tracelog --addModPath \"$(ProjectDir)/bin/$(Configuration)/Mods\" --addOrigin \"$(ProjectDir)/assets\"$(VintageStoryDataPathArgs)",
|
||||||
|
"workingDirectory": "$(VINTAGE_STORY)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user