reinit branch
This commit is contained in:
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)"
|
||||
}
|
||||
}
|
||||
}
|
||||
83
RoamingBees/RoamingBees/RoamingBees.csproj
Normal file
83
RoamingBees/RoamingBees/RoamingBees.csproj
Normal file
@@ -0,0 +1,83 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||
<OutputPath>bin\$(Configuration)\Mods\roamingbees</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>
|
||||
<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>
|
||||
96
RoamingBees/RoamingBees/RoamingBeesModSystem.cs
Normal file
96
RoamingBees/RoamingBees/RoamingBeesModSystem.cs
Normal file
@@ -0,0 +1,96 @@
|
||||
using RoamingBees.Behaviors;
|
||||
using RoamingBees.Particles;
|
||||
using RoamingBees.Particles.Catchup;
|
||||
using System;
|
||||
using Vintagestory.API.Client;
|
||||
using Vintagestory.API.Common;
|
||||
using Vintagestory.API.Server;
|
||||
|
||||
namespace RoamingBees;
|
||||
|
||||
public partial class RoamingBeesModSystem : ModSystem
|
||||
{
|
||||
private ICoreServerAPI? serverApi;
|
||||
private ICoreClientAPI? clientApi;
|
||||
private ICoreAPI? api;
|
||||
public const string CONFIG_CHANNEL_NAME = "roamingbees-config";
|
||||
|
||||
internal BeeSpawnPacketDistributor? BeeSpawnPacketDistributor { get; private set; }
|
||||
internal int GlobalActiveBees { get; set; }
|
||||
internal bool DebugPathsEnabled { get; set; }
|
||||
public IServerNetworkChannel? ServerChannel { get; private set; }
|
||||
public IClientNetworkChannel? ClientChannel { get; private set; }
|
||||
|
||||
public override void Start(ICoreAPI api)
|
||||
{
|
||||
this.api = api;
|
||||
api.RegisterBlockBehaviorClass(nameof(BlockBehaviorRoamingBees), typeof(BlockBehaviorRoamingBees));
|
||||
api.RegisterBlockEntityBehaviorClass(nameof(BlockEntityBehaviorRoamingBees), typeof(BlockEntityBehaviorRoamingBees));
|
||||
api.RegisterBlockBehaviorClass(nameof(BlockBehaviorBeeSwarm), typeof(BlockBehaviorBeeSwarm));
|
||||
api.RegisterBlockEntityBehaviorClass(nameof(BlockEntityBehaviorBeeSwarm), typeof(BlockEntityBehaviorBeeSwarm));
|
||||
|
||||
try
|
||||
{
|
||||
if (api.ModLoader.IsModSystemEnabled("ConfigLib.ConfigLibModSystem"))
|
||||
SubscribeToConfigChange(api);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
Mod.Logger.VerboseDebug("Failed to subscribe to config change");
|
||||
}
|
||||
}
|
||||
|
||||
public override void StartServerSide(ICoreServerAPI serverApi)
|
||||
{
|
||||
InitializeServerConfig(serverApi);
|
||||
this.serverApi = serverApi;
|
||||
SetupServerCommands(serverApi);
|
||||
BeeSpawnPacketDistributor = new(this, serverApi.Side);
|
||||
ServerChannel = serverApi.Network
|
||||
.RegisterChannel(BlockEntityBehaviorRoamingBees.NETWORK_CHANNEL_NAME)
|
||||
.RegisterMessageType<BeeSpawnPacket>()
|
||||
.RegisterMessageType<BeeCatchupRequestPacket>().SetMessageHandler<BeeCatchupRequestPacket>(BeeSpawnPacketDistributor.OnBeeCatchupRequest)
|
||||
.RegisterMessageType<BeeCatchupPacket>();
|
||||
serverApi.Network
|
||||
.RegisterChannel(CONFIG_CHANNEL_NAME)
|
||||
.RegisterMessageType<Config>();
|
||||
}
|
||||
|
||||
public override void StartClientSide(ICoreClientAPI clientApi)
|
||||
{
|
||||
this.clientApi = clientApi;
|
||||
InitializeClientConfig(clientApi);
|
||||
SetupClientCommands(clientApi);
|
||||
BeeSpawnPacketDistributor = new(this, clientApi.Side);
|
||||
ClientChannel = clientApi.Network
|
||||
// in case something ever magically doesn't work.. the docs say:
|
||||
// "Must be registered in the same order as on the server."
|
||||
.RegisterChannel(BlockEntityBehaviorRoamingBees.NETWORK_CHANNEL_NAME)
|
||||
.RegisterMessageType<BeeSpawnPacket>()
|
||||
.RegisterMessageType<BeeCatchupRequestPacket>().SetMessageHandler<BeeSpawnPacket>(BeeSpawnPacketDistributor.HandleBeeParticleSpawn)
|
||||
.RegisterMessageType<BeeCatchupPacket>().SetMessageHandler<BeeCatchupPacket>(BeeSpawnPacketDistributor.HandleBeeParticleCatchup);
|
||||
|
||||
clientApi.Network
|
||||
.RegisterChannel(CONFIG_CHANNEL_NAME)
|
||||
.RegisterMessageType<Config>()
|
||||
.SetMessageHandler<Config>(OnConfigReceivedFromServer);
|
||||
}
|
||||
|
||||
public override double ExecuteOrder() => 0.12;
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
SetDebugPathsEnabled(false);
|
||||
BeeSpawnPacketDistributor?.Clear();
|
||||
if (serverApi != null)
|
||||
serverApi.Event.PlayerJoin -= OnPlayerJoin;
|
||||
if (api?.ModLoader.IsModSystemEnabled("ConfigLib.ConfigLibModSystem") == true)
|
||||
UnsubscribeFromConfigChange();
|
||||
}
|
||||
|
||||
internal void SetDebugPathsEnabled(bool enabled)
|
||||
{
|
||||
DebugPathsEnabled = enabled;
|
||||
DebugPathRenderManager.SetEnabled(clientApi, enabled);
|
||||
}
|
||||
}
|
||||
10
RoamingBees/RoamingBees/Utilities/ApiExtensions.cs
Normal file
10
RoamingBees/RoamingBees/Utilities/ApiExtensions.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using OrekiWoofsBees.Common;
|
||||
using Vintagestory.API.Common;
|
||||
|
||||
namespace RoamingBees.Utilities;
|
||||
|
||||
internal static class ApiExtensions
|
||||
{
|
||||
public static IPlantPositionRegistry? GetPlantPositionRegistry(this ICoreAPI api) => api.ModLoader.GetModSystem<PlantPositionRegistryModSystem2>();
|
||||
public static RoamingBeesModSystem? GetFlyingBeesModSystem(this ICoreAPI api) => api.ModLoader.GetModSystem<RoamingBeesModSystem>();
|
||||
}
|
||||
41
RoamingBees/RoamingBees/Utilities/VectorParsing.cs
Normal file
41
RoamingBees/RoamingBees/Utilities/VectorParsing.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using Vintagestory.API.Datastructures;
|
||||
|
||||
namespace RoamingBees.Utilities;
|
||||
|
||||
internal static class VectorParsing
|
||||
{
|
||||
public static Vector3? TryParseVector3(JsonObject? obj)
|
||||
{
|
||||
if (obj is null || obj.Token is null)
|
||||
return null;
|
||||
|
||||
var values = obj.AsObject<float[]>([]);
|
||||
if (values.Length < 3)
|
||||
return null;
|
||||
|
||||
return new Vector3(values[0], values[1], values[2]);
|
||||
}
|
||||
|
||||
public static Dictionary<string, Vector3>? ParseVector3Map(JsonObject? obj)
|
||||
{
|
||||
if (obj is null || obj.Token is null)
|
||||
return null;
|
||||
|
||||
var raw = obj.AsObject<Dictionary<string, float[]>>([]);
|
||||
if (raw.Count == 0)
|
||||
return null;
|
||||
|
||||
var result = new Dictionary<string, Vector3>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var kvp in raw)
|
||||
{
|
||||
if (kvp.Value is null || kvp.Value.Length < 3)
|
||||
continue;
|
||||
result[kvp.Key] = new Vector3(kvp.Value[0], kvp.Value[1], kvp.Value[2]);
|
||||
}
|
||||
|
||||
return result.Count > 0 ? result : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"version": 0,
|
||||
"file": "RoamingBees.json",
|
||||
"patches": {},
|
||||
"settings": [
|
||||
{
|
||||
"type": "separator",
|
||||
"title": "Client-side"
|
||||
},
|
||||
{
|
||||
"code": "ReceiveParticles",
|
||||
"comment": "Whether to spawn and show bee particles.",
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"clientSide": true
|
||||
},
|
||||
{
|
||||
"type": "separator",
|
||||
"title": "General"
|
||||
},
|
||||
{
|
||||
"code": "EnableOnVanillaSkeps",
|
||||
"comment": "Whether roaming bee models will come out of vanilla skeps.",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"code": "RoamingBeesPerVanillaSkep",
|
||||
"comment": "Max amount of bees roaming from a single vanilla skep.",
|
||||
"type": "int",
|
||||
"default": 15,
|
||||
"range": {
|
||||
"min": 0,
|
||||
"max": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "BeeRoamingRadius",
|
||||
"comment": "The radius within which bees visit flowers. Set to 0 to use the source's radius attribute. If this is 0, and the source doesn't have this attribute, it defaults to 10.",
|
||||
"type": "int",
|
||||
"default": 0,
|
||||
"range": {
|
||||
"min": 0,
|
||||
"max": 70
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "MaxGlobalRoamingBees",
|
||||
"comment": "Maximum number of bee particle groups across all sources.",
|
||||
"type": "int",
|
||||
"default": 200,
|
||||
"range": {
|
||||
"min": 0,
|
||||
"max": 1000
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "separator",
|
||||
"title": "From Golden Combs compatibility"
|
||||
},
|
||||
{
|
||||
"code": "EnableOnFgcCeramic",
|
||||
"comment": "Whether roaming bee models will come out of Ceramic Hives from the 'From Golden Combs' mod.",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"code": "RoamingBeesPerFgcCeramic",
|
||||
"comment": "Max amount of bees roaming from a single Ceramic Hive from the 'From Golden Combs' mod.",
|
||||
"type": "int",
|
||||
"default": 15,
|
||||
"range": {
|
||||
"min": 0,
|
||||
"max": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "EnableOnFgcLangstroth",
|
||||
"comment": "Whether roaming bee models will come out of Langstroth Hives from the 'From Golden Combs' mod.",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"code": "RoamingBeesPerFgcLangstroth",
|
||||
"comment": "Max amount of bees roaming from a single Langstroth Hive from the 'From Golden Combs' mod.",
|
||||
"type": "int",
|
||||
"default": 25,
|
||||
"range": {
|
||||
"min": 0,
|
||||
"max": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "separator",
|
||||
"title": "Swarms"
|
||||
},
|
||||
{
|
||||
"code": "BeesPerSwarmHovering",
|
||||
"comment": "How many bees hang out around a swarm block.",
|
||||
"type": "int",
|
||||
"default": 15,
|
||||
"range": {
|
||||
"min": 0,
|
||||
"max": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "BeesPerSwarmTraveling",
|
||||
"comment": "How many bees travel between a hive and a swarm block during migrations.",
|
||||
"type": "int",
|
||||
"default": 15,
|
||||
"range": {
|
||||
"min": 0,
|
||||
"max": 50
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "separator",
|
||||
"title": "Weather Effects"
|
||||
},
|
||||
{
|
||||
"code": "RainfallSpawnStopThreshold",
|
||||
"comment": "Rainfall level at which bee spawning completely stops.",
|
||||
"type": "float",
|
||||
"default": 0.1,
|
||||
"range": {
|
||||
"min": 0.0,
|
||||
"max": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "MaxRainfallCooldownPenalty",
|
||||
"comment": "Maximum seconds added to spawn cooldown at rainfall threshold.",
|
||||
"type": "float",
|
||||
"default": 60.0,
|
||||
"range": {
|
||||
"min": 0.0,
|
||||
"max": 300.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "GreenhouseAffectsBeehive",
|
||||
"comment": "Whether greenhouses give 5C degrees temperature boost to the beehive.",
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
{
|
||||
"code": "MinTemperatureParticleSpawn",
|
||||
"comment": "Temperature at which bee particle spawn chance drops to zero.",
|
||||
"type": "float",
|
||||
"default": 8.0,
|
||||
"range": {
|
||||
"min": -10.0,
|
||||
"max": 30.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "MaxTemperatureParticleSpawn",
|
||||
"comment": "Temperature at which bee particle spawn chance is optimal.",
|
||||
"type": "float",
|
||||
"default": 18.0,
|
||||
"range": {
|
||||
"min": 0.0,
|
||||
"max": 40.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "OptimalTemperatureSpawnChance",
|
||||
"comment": "UNUSED ! Random spawn chance threshold at optimal temperature (lower = easier to spawn).",
|
||||
"type": "float",
|
||||
"default": 0.75,
|
||||
"range": {
|
||||
"min": 0.0,
|
||||
"max": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "BaseWindFrequency",
|
||||
"comment": "Base frequency for particle movement sinusoidal noise at no wind.",
|
||||
"type": "float",
|
||||
"default": 0.1,
|
||||
"range": {
|
||||
"min": 0.01,
|
||||
"max": 1.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "WindFrequencyMultiplier",
|
||||
"comment": "Multiplier for how much wind increases noise frequency (4 = 5x faster at max wind).",
|
||||
"type": "float",
|
||||
"default": 4.0,
|
||||
"range": {
|
||||
"min": 0.0,
|
||||
"max": 10.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "BaseWindNoiseStrength",
|
||||
"comment": "Base acceleration strength from noise at no wind.",
|
||||
"type": "float",
|
||||
"default": 0.2,
|
||||
"range": {
|
||||
"min": 0.0,
|
||||
"max": 2.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "MaxWindNoiseStrength",
|
||||
"comment": "Maximum acceleration strength from noise at full wind.",
|
||||
"type": "float",
|
||||
"default": 1.0,
|
||||
"range": {
|
||||
"min": 0.0,
|
||||
"max": 5.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "SunAltitudeMinDegrees",
|
||||
"comment": "Minimum sun altitude in degrees for spawn modifier calculation (0 = horizon).",
|
||||
"type": "float",
|
||||
"default": -5.0,
|
||||
"range": {
|
||||
"min": -90.0,
|
||||
"max": 90.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "SunAltitudeRangeDegrees",
|
||||
"comment": "Range in degrees for sun altitude modifier (from min to min+range = 0% to 100%).",
|
||||
"type": "float",
|
||||
"default": 10.0,
|
||||
"range": {
|
||||
"min": 1.0,
|
||||
"max": 90.0
|
||||
}
|
||||
},
|
||||
{
|
||||
"code": "MaxSunAltitudeCooldownPenalty",
|
||||
"comment": "Maximum cooldown penalty in seconds at minimum sun altitude (linearly decreases to 0 at max altitude).",
|
||||
"type": "float",
|
||||
"default": 30.0,
|
||||
"range": {
|
||||
"min": 0.0,
|
||||
"max": 300.0
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
29
RoamingBees/RoamingBees/assets/roamingbees/lang/en.json
Normal file
29
RoamingBees/RoamingBees/assets/roamingbees/lang/en.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"config-desc-ReceiveParticles": "Whether to receive and render roaming bee particles on the client.",
|
||||
"config-desc-EnableOnVanillaSkeps": "Whether roaming bees spawn from vanilla skeps.",
|
||||
"config-desc-RoamingBeesPerVanillaSkep": "Number of roaming bees per vanilla skep.",
|
||||
"config-desc-EnableOnFgcCeramic": "Whether roaming bees spawn from From Golden Combs ceramic hives.",
|
||||
"config-desc-RoamingBeesPerFgcCeramic": "Number of roaming bees per From Golden Combs ceramic hive.",
|
||||
"config-desc-EnableOnFgcLangstroth": "Whether roaming bees spawn from From Golden Combs Langstroth hives.",
|
||||
"config-desc-RoamingBeesPerFgcLangstroth": "Number of roaming bees per From Golden Combs Langstroth hive.",
|
||||
"config-desc-BeeRoamingRadius": "Radius in blocks within which bees roam from their hive.",
|
||||
"config-desc-RainfallSpawnStopThreshold": "Rain level (0-1) above which bee spawning stops.",
|
||||
"config-desc-MaxRainfallCooldownPenalty": "Maximum cooldown penalty in seconds after rain stops before bees resume spawning.",
|
||||
"config-desc-GreenhouseAffectsBeehive": "Whether being in a greenhouse affects roaming bee behavior.",
|
||||
"config-desc-MinTemperatureParticleSpawn": "Minimum temperature (°C) at which bee particles start spawning.",
|
||||
"config-desc-MaxTemperatureParticleSpawn": "Temperature (°C) at which bee particle spawn chance reaches maximum.",
|
||||
"config-desc-OptimalTemperatureSpawnChance": "Spawn chance (0-1) at optimal temperature.",
|
||||
"config-desc-BaseWindFrequency": "Base frequency of wind effect on bee movement.",
|
||||
"config-desc-WindFrequencyMultiplier": "Multiplier applied to wind frequency.",
|
||||
"config-desc-BaseWindNoiseStrength": "Base noise strength of wind effect on bee movement.",
|
||||
"config-desc-MaxWindNoiseStrength": "Maximum noise strength of wind effect on bee movement.",
|
||||
"config-desc-SunAltitudeMinDegrees": "Minimum sun altitude in degrees for bee spawning.",
|
||||
"config-desc-SunAltitudeRangeDegrees": "Range of sun altitude degrees over which spawn chance ramps up.",
|
||||
"config-desc-MaxSunAltitudeCooldownPenalty": "Maximum cooldown penalty in seconds when sun is below minimum altitude.",
|
||||
"config-desc-MaxGlobalRoamingBees": "Maximum number of roaming bee particles active globally.",
|
||||
"debugpaths-desc": "Toggle client-side debug rendering of target boxes around nearby blocks (0 or 1).",
|
||||
"debugpaths-parse-error": "Couldn't parse. Use 0 or 1.",
|
||||
"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."
|
||||
}
|
||||
29
RoamingBees/RoamingBees/assets/roamingbees/lang/ru.json
Normal file
29
RoamingBees/RoamingBees/assets/roamingbees/lang/ru.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"config-desc-ReceiveParticles": "Принимать ли и отображать частицы бродящих пчёл на клиенте.",
|
||||
"config-desc-EnableOnVanillaSkeps": "Появляются ли бродячие пчёлы из ванильных ульев.",
|
||||
"config-desc-RoamingBeesPerVanillaSkep": "Количество бродячих пчёл на ванильный улей.",
|
||||
"config-desc-EnableOnFgcCeramic": "Появляются ли бродячие пчёлы из керамических ульев «From Golden Combs».",
|
||||
"config-desc-RoamingBeesPerFgcCeramic": "Количество бродячих пчёл в керамическом улье «From Golden Combs».",
|
||||
"config-desc-EnableOnFgcLangstroth": "Появляются ли бродячие пчёлы из ульев «Golden Combs Langstroth».",
|
||||
"config-desc-RoamingBeesPerFgcLangstroth": "Количество бродячих пчёл в керамическом улье «Golden Combs Langstroth».",
|
||||
"config-desc-BeeRoamingRadius": "Радиус в блоках от улья, внутри которых бродят пчёлы.",
|
||||
"config-desc-RainfallSpawnStopThreshold": "Уровень осадков (0-1), выше которого прекращается появление пчёл.",
|
||||
"config-desc-MaxRainfallCooldownPenalty": "Максимальное время в секундах после прекращения дождя до того, как пчёлы вновь начнут появляться.",
|
||||
"config-desc-GreenhouseAffectsBeehive": "Влияет ли нахождение в теплице на поведение пчёл.",
|
||||
"config-desc-MinTemperatureParticleSpawn": "Минимальная температура (°C), при которой пчёлы начинают появляться.",
|
||||
"config-desc-MaxTemperatureParticleSpawn": "Температура (°C), при которой вероятность появления пчёл достигает максимума.",
|
||||
"config-desc-OptimalTemperatureSpawnChance": "Шанс появления (0-1) при оптимальной температуре.",
|
||||
"config-desc-BaseWindFrequency": "Базовая частота влияния ветра на движение пчёл.",
|
||||
"config-desc-WindFrequencyMultiplier": "Множитель, применяемый к частоте ветра.",
|
||||
"config-desc-BaseWindNoiseStrength": "Базовая сила шума ветра, влияющего на движение пчёл.",
|
||||
"config-desc-MaxWindNoiseStrength": "Максимальная сила шума ветра, влияющего на движение пчёл.",
|
||||
"config-desc-SunAltitudeMinDegrees": "Минимальная высота солнца в градусах для появления пчёл.",
|
||||
"config-desc-SunAltitudeRangeDegrees": "Диапазон высоты солнца в градусах, в пределах которого постепенно увеличивается шанс появления пчёл.",
|
||||
"config-desc-MaxSunAltitudeCooldownPenalty": "Максимальный штраф к перезарядке в секундах, когда солнце ниже минимальной высоты.",
|
||||
"config-desc-MaxGlobalRoamingBees": "Максимальное глобальное количество частиц блуждающих пчёл.",
|
||||
"debugpaths-desc": "Включить или выключить клиентскую отладочную визуализацию целевых рамок вокруг ближайших блоков (0 или 1).",
|
||||
"debugpaths-parse-error": "Не удалось разобрать значение. Используйте 0 или 1.",
|
||||
"plantreg-blockpertick-desc": "Получить или установить BlocksPerTick сканирования реестра растений (0–1000).",
|
||||
"plantreg-blockpertick-parse-error": "Не удалось разобрать значение. Используйте целое число в диапазоне 0–1000.",
|
||||
"plantreg-unavailable": "Система реестра растений недоступна."
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"op": "addMerge",
|
||||
"path": "/entityBehaviors",
|
||||
"value": [{"name": "BlockEntityBehaviorBeeSwarm"}],
|
||||
"file": "orekiwoofsbeehives:blocktypes/beeswarm",
|
||||
"side": "Server",
|
||||
"dependsOn": [{"modid": "orekiwoofsbeehives"}]
|
||||
},
|
||||
{
|
||||
"op": "addMerge",
|
||||
"path": "/behaviors",
|
||||
"value": [{"name": "BlockBehaviorBeeSwarm"}],
|
||||
"file": "orekiwoofsbeehives:blocktypes/beeswarm",
|
||||
"side": "Server",
|
||||
"dependsOn": [{"modid": "orekiwoofsbeehives"}]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,145 @@
|
||||
[
|
||||
{
|
||||
"op": "addMerge",
|
||||
"path": "/entityBehaviors",
|
||||
"value": [
|
||||
{
|
||||
"name": "BlockEntityBehaviorRoamingBees",
|
||||
"properties": {
|
||||
"facingVariantKey": "",
|
||||
"entrancePositions": {
|
||||
"north": [0.5, 0.1, 0.8],
|
||||
"east": [0.2, 0.1, 0.5],
|
||||
"south": [0.5, 0.1, 0.2],
|
||||
"west": [0.8, 0.1, 0.5]
|
||||
},
|
||||
"frontDirections": {
|
||||
"north": [0, 0, 1],
|
||||
"east": [-1, 0, 0],
|
||||
"south": [0, 0, -1],
|
||||
"west": [1, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"file": "fromgoldencombs:blocktypes/ceramicpot/ceramicbroodpot",
|
||||
"side": "Server",
|
||||
"dependsOn": [
|
||||
{
|
||||
"modid": "fromgoldencombs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "addMerge",
|
||||
"path": "/behaviorsByType/*",
|
||||
"value": [
|
||||
{
|
||||
"name": "BlockBehaviorRoamingBees"
|
||||
}
|
||||
],
|
||||
"file": "fromgoldencombs:blocktypes/ceramicpot/ceramicbroodpot",
|
||||
"side": "Server",
|
||||
"dependsOn": [
|
||||
{
|
||||
"modid": "fromgoldencombs"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"op": "addMerge",
|
||||
"path": "/entityBehaviors",
|
||||
"value": [
|
||||
{
|
||||
"name": "BlockEntityBehaviorRoamingBees",
|
||||
"properties": {
|
||||
"facingVariantKey": "",
|
||||
"entrancePositions": {
|
||||
"north": [0.5, 0.1, 0.8],
|
||||
"east": [0.2, 0.1, 0.5],
|
||||
"south": [0.5, 0.1, 0.2],
|
||||
"west": [0.8, 0.1, 0.5]
|
||||
},
|
||||
"frontDirections": {
|
||||
"north": [0, 0, 1],
|
||||
"east": [-1, 0, 0],
|
||||
"south": [0, 0, -1],
|
||||
"west": [1, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"file": "fromgoldencombs:blocktypes/ceramicpot/ceramicbroodpot-new",
|
||||
"side": "Server",
|
||||
"dependsOn": [
|
||||
{
|
||||
"modid": "fromgoldencombs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "addMerge",
|
||||
"path": "/behaviorsByType/*",
|
||||
"value": [
|
||||
{
|
||||
"name": "BlockBehaviorRoamingBees"
|
||||
}
|
||||
],
|
||||
"file": "fromgoldencombs:blocktypes/ceramicpot/ceramicbroodpot-new",
|
||||
"side": "Server",
|
||||
"dependsOn": [
|
||||
{
|
||||
"modid": "fromgoldencombs"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"op": "addMerge",
|
||||
"path": "/entityBehaviors",
|
||||
"value": [
|
||||
{
|
||||
"name": "BlockEntityBehaviorRoamingBees",
|
||||
"properties": {
|
||||
"facingVariantKey": "",
|
||||
"entrancePositions": {
|
||||
"north": [0.5, 0.1, 0.8],
|
||||
"east": [0.2, 0.1, 0.5],
|
||||
"south": [0.5, 0.1, 0.2],
|
||||
"west": [0.8, 0.1, 0.5]
|
||||
},
|
||||
"frontDirections": {
|
||||
"north": [0, 0, 1],
|
||||
"east": [-1, 0, 0],
|
||||
"south": [0, 0, -1],
|
||||
"west": [1, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"file": "fromgoldencombs:blocktypes/ceramicpot/ceramicbroodpot-fancy",
|
||||
"side": "Server",
|
||||
"dependsOn": [
|
||||
{
|
||||
"modid": "fromgoldencombs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "addMerge",
|
||||
"path": "/behaviorsByType/*",
|
||||
"value": [
|
||||
{
|
||||
"name": "BlockBehaviorRoamingBees"
|
||||
}
|
||||
],
|
||||
"file": "fromgoldencombs:blocktypes/ceramicpot/ceramicbroodpot-fancy",
|
||||
"side": "Server",
|
||||
"dependsOn": [
|
||||
{
|
||||
"modid": "fromgoldencombs"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,49 @@
|
||||
[
|
||||
{
|
||||
"op": "addMerge",
|
||||
"path": "/entityBehaviors",
|
||||
"value": [
|
||||
{
|
||||
"name": "BlockEntityBehaviorRoamingBees",
|
||||
"properties": {
|
||||
"facingVariantKey": "side",
|
||||
"entrancePositions": {
|
||||
"north": [0.5, 0.27, 0.7],
|
||||
"east": [0.3, 0.27, 0.5],
|
||||
"south": [0.5, 0.27, 0.3],
|
||||
"west": [0.7, 0.27, 0.5]
|
||||
},
|
||||
"frontDirections": {
|
||||
"north": [0, -0.5, 1],
|
||||
"east": [-1, -0.5, 0],
|
||||
"south": [0, -0.5, -1],
|
||||
"west": [1, -0.5, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"file": "fromgoldencombs:blocktypes/langstroth/langstrothstack",
|
||||
"side": "Server",
|
||||
"dependsOn": [
|
||||
{
|
||||
"modid": "fromgoldencombs"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "addMerge",
|
||||
"path": "/behaviors",
|
||||
"value": [
|
||||
{
|
||||
"name": "BlockBehaviorRoamingBees"
|
||||
}
|
||||
],
|
||||
"file": "fromgoldencombs:blocktypes/langstroth/langstrothstack",
|
||||
"side": "Server",
|
||||
"dependsOn": [
|
||||
{
|
||||
"modid": "fromgoldencombs"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,49 @@
|
||||
[
|
||||
{
|
||||
"op": "addMerge",
|
||||
"path": "/entityBehaviors",
|
||||
"value": [
|
||||
{
|
||||
"name": "BlockEntityBehaviorRoamingBees",
|
||||
"properties": {
|
||||
"facingVariantKey": "side",
|
||||
"entrancePositions": {
|
||||
"north": [0.5, 0.2, 0.9],
|
||||
"east": [0.1, 0.2, 0.5],
|
||||
"south": [0.5, 0.2, 0.1],
|
||||
"west": [0.9, 0.2, 0.5]
|
||||
},
|
||||
"frontDirections": {
|
||||
"north": [0, 0, 1],
|
||||
"east": [-1, 0, 0],
|
||||
"south": [0, 0, -1],
|
||||
"west": [1, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"file": "orekiwoofsbeehives:blocktypes/beehive",
|
||||
"side": "Server",
|
||||
"dependsOn": [
|
||||
{
|
||||
"modid": "orekiwoofsbeehives"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "addMerge",
|
||||
"path": "/behaviors",
|
||||
"value": [
|
||||
{
|
||||
"name": "BlockBehaviorRoamingBees"
|
||||
}
|
||||
],
|
||||
"file": "orekiwoofsbeehives:blocktypes/beehive",
|
||||
"side": "Server",
|
||||
"dependsOn": [
|
||||
{
|
||||
"modid": "orekiwoofsbeehives"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
39
RoamingBees/RoamingBees/assets/roamingbees/patches/skep.json
Normal file
39
RoamingBees/RoamingBees/assets/roamingbees/patches/skep.json
Normal file
@@ -0,0 +1,39 @@
|
||||
[
|
||||
{
|
||||
"op": "addMerge",
|
||||
"path": "/entityBehaviors",
|
||||
"value": [
|
||||
{
|
||||
"name": "BlockEntityBehaviorRoamingBees",
|
||||
"properties": {
|
||||
"facingVariantKey": "side",
|
||||
"entrancePositions": {
|
||||
"north": [0.5, 0.1, 0.6],
|
||||
"east": [0.4, 0.1, 0.5],
|
||||
"south": [0.5, 0.1, 0.4],
|
||||
"west": [0.6, 0.1, 0.5]
|
||||
},
|
||||
"frontDirections": {
|
||||
"north": [0, 0, 1],
|
||||
"east": [-1, 0, 0],
|
||||
"south": [0, 0, -1],
|
||||
"west": [1, 0, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"file": "game:blocktypes/reed/skep",
|
||||
"side": "Server"
|
||||
},
|
||||
{
|
||||
"op": "addMerge",
|
||||
"path": "/behaviors",
|
||||
"value": [
|
||||
{
|
||||
"name": "BlockBehaviorRoamingBees"
|
||||
}
|
||||
],
|
||||
"file": "game:blocktypes/reed/skep",
|
||||
"side": "Server"
|
||||
}
|
||||
]
|
||||
BIN
RoamingBees/RoamingBees/modicon.png
Normal file
BIN
RoamingBees/RoamingBees/modicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7.4 KiB |
14
RoamingBees/RoamingBees/modinfo.json
Normal file
14
RoamingBees/RoamingBees/modinfo.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://moddbcdn.vintagestory.at/schema/modinfo.latest.json",
|
||||
"type": "Code",
|
||||
"modid": "roamingbees",
|
||||
"name": "OrekiWoof's Roaming Bees",
|
||||
"authors": [
|
||||
"OrekiWoof"
|
||||
],
|
||||
"description": "Cute immersive roaming bees. Now on vanilla skeps and other mods' hives.",
|
||||
"version": "2.0.0-dev.5",
|
||||
"dependencies": {
|
||||
"game": "1.21.0"
|
||||
}
|
||||
}
|
||||
122
RoamingBees/ZZCakeBuild/Program.cs
Normal file
122
RoamingBees/ZZCakeBuild/Program.cs
Normal file
@@ -0,0 +1,122 @@
|
||||
using Cake.Common;
|
||||
using Cake.Common.IO;
|
||||
using Cake.Common.Tools.DotNet;
|
||||
using Cake.Common.Tools.DotNet.Clean;
|
||||
using Cake.Common.Tools.DotNet.Publish;
|
||||
using Cake.Core;
|
||||
using Cake.Frosting;
|
||||
using Cake.Json;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Vintagestory.API.Common;
|
||||
|
||||
namespace RoamingBeesCakeBuild;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static int Main(string[] args)
|
||||
{
|
||||
return new CakeHost()
|
||||
.UseContext<BuildContext>()
|
||||
.Run(args);
|
||||
}
|
||||
}
|
||||
|
||||
public class BuildContext : FrostingContext
|
||||
{
|
||||
public const string PROJECT_NAME = "RoamingBees";
|
||||
public string BuildConfiguration { get; }
|
||||
public string Version { get; }
|
||||
public string Name { get; }
|
||||
public bool SkipJsonValidation { get; }
|
||||
public string GameVersion { get; }
|
||||
|
||||
public BuildContext(ICakeContext context)
|
||||
: base(context)
|
||||
{
|
||||
BuildConfiguration = context.Argument("configuration", "Release");
|
||||
SkipJsonValidation = context.Argument("skipJsonValidation", false);
|
||||
var modInfo = context.DeserializeJsonFromFile<ModInfo>($"../{PROJECT_NAME}/modinfo.json");
|
||||
Version = modInfo.Version;
|
||||
Name = modInfo.ModID;
|
||||
GameVersion = modInfo.Dependencies.First(x => x.ModID == "game").Version;
|
||||
}
|
||||
}
|
||||
|
||||
[TaskName("ValidateJson")]
|
||||
public sealed class ValidateJsonTask : FrostingTask<BuildContext>
|
||||
{
|
||||
public override void Run(BuildContext context)
|
||||
{
|
||||
if (context.SkipJsonValidation)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var jsonFiles = context.GetFiles($"../{BuildContext.PROJECT_NAME}/assets/**/*.json");
|
||||
foreach (var file in jsonFiles)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = File.ReadAllText(file.FullPath);
|
||||
JToken.Parse(json);
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
throw new Exception($"Validation failed for JSON file: {file.FullPath}{Environment.NewLine}{ex.Message}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[TaskName("Build")]
|
||||
[IsDependentOn(typeof(ValidateJsonTask))]
|
||||
public sealed class BuildTask : FrostingTask<BuildContext>
|
||||
{
|
||||
public override void Run(BuildContext context)
|
||||
{
|
||||
context.DotNetClean($"../{BuildContext.PROJECT_NAME}/{BuildContext.PROJECT_NAME}.csproj",
|
||||
new DotNetCleanSettings
|
||||
{
|
||||
Configuration = context.BuildConfiguration
|
||||
});
|
||||
|
||||
|
||||
context.DotNetPublish($"../{BuildContext.PROJECT_NAME}/{BuildContext.PROJECT_NAME}.csproj",
|
||||
new DotNetPublishSettings
|
||||
{
|
||||
Configuration = context.BuildConfiguration
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[TaskName("Package")]
|
||||
[IsDependentOn(typeof(BuildTask))]
|
||||
public sealed class PackageTask : FrostingTask<BuildContext>
|
||||
{
|
||||
public override void Run(BuildContext context)
|
||||
{
|
||||
context.EnsureDirectoryExists("../Releases");
|
||||
context.CleanDirectory("../Releases");
|
||||
context.EnsureDirectoryExists($"../Releases/{context.Name}");
|
||||
context.CopyFiles($"../{BuildContext.PROJECT_NAME}/bin/{context.BuildConfiguration}/Mods/roamingbees/publish/*", $"../Releases/{context.Name}");
|
||||
if (context.DirectoryExists($"../{BuildContext.PROJECT_NAME}/assets"))
|
||||
{
|
||||
context.CopyDirectory($"../{BuildContext.PROJECT_NAME}/assets", $"../Releases/{context.Name}/assets");
|
||||
}
|
||||
context.CopyFile($"../{BuildContext.PROJECT_NAME}/modinfo.json", $"../Releases/{context.Name}/modinfo.json");
|
||||
if (context.FileExists($"../{BuildContext.PROJECT_NAME}/modicon.png"))
|
||||
{
|
||||
context.CopyFile($"../{BuildContext.PROJECT_NAME}/modicon.png", $"../Releases/{context.Name}/modicon.png");
|
||||
}
|
||||
context.Zip($"../Releases/{context.Name}", $"../Releases/{context.Name}_v{context.Version}-v{context.GameVersion}.zip");
|
||||
}
|
||||
}
|
||||
|
||||
[TaskName("Default")]
|
||||
[IsDependentOn(typeof(PackageTask))]
|
||||
public class DefaultTask : FrostingTask
|
||||
{
|
||||
}
|
||||
20
RoamingBees/ZZCakeBuild/RoamingBeesCakeBuild.csproj
Normal file
20
RoamingBees/ZZCakeBuild/RoamingBeesCakeBuild.csproj
Normal file
@@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||
<Configurations>Debug;Release;Debug22</Configurations>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Cake.Frosting" Version="5.0.0" />
|
||||
<PackageReference Include="Cake.Json" Version="7.0.1" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="VintagestoryAPI">
|
||||
<HintPath>$(VINTAGE_STORY)/VintagestoryAPI.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
2
RoamingBees/build.ps1
Normal file
2
RoamingBees/build.ps1
Normal file
@@ -0,0 +1,2 @@
|
||||
dotnet run --project ZZCakeBuild/RoamingBeesCakeBuild.csproj -- $args
|
||||
exit $LASTEXITCODE;
|
||||
1
RoamingBees/build.sh
Normal file
1
RoamingBees/build.sh
Normal file
@@ -0,0 +1 @@
|
||||
dotnet run --project ./ZZCakeBuild/RoamingBeesCakeBuild.csproj -- "$@"
|
||||
Reference in New Issue
Block a user