From 3ca06726c070c5b766fd384487c118d991051f8a Mon Sep 17 00:00:00 2001 From: Marc Hernandez Date: Sun, 1 Feb 2026 23:07:15 -0800 Subject: [PATCH] Moved core into my new style --- .gitignore | 1 + addons/core/.gitattributes | 285 +++++++++ addons/core/.gitignore | 67 +++ addons/core/ai/AStarGraph.cs | 106 ++++ addons/core/ai/AStarGraph.cs.uid | 1 + addons/core/ai/AStarSolver.cs | 79 +++ addons/core/ai/AStarSolver.cs.uid | 1 + addons/core/ai/SpherePathFollower.cs | 175 ++++++ addons/core/ai/SpherePathFollower.cs.uid | 1 + addons/core/core.cs | 219 +++++++ addons/core/core.cs.uid | 1 + addons/core/core/Game.cs | 391 +++++++++++++ addons/core/core/Game.cs.uid | 1 + addons/core/core/Game.tscn | 38 ++ addons/core/core/MPNode.cs | 28 + addons/core/core/MPNode.cs.uid | 1 + addons/core/core/NodeSpace.cs | 52 ++ addons/core/core/NodeSpace.cs.uid | 1 + addons/core/fsm/FSMNode.cs | 86 +++ addons/core/fsm/FSMNode.cs.uid | 1 + addons/core/fsm/StateNode.cs | 47 ++ addons/core/fsm/StateNode.cs.uid | 1 + addons/core/math/CentEx.cs | 22 + addons/core/math/CentEx.cs.uid | 1 + addons/core/math/Geo.cs | 152 +++++ addons/core/math/Geo.cs.uid | 1 + addons/core/math/MathUtil.cs | 67 +++ addons/core/math/MathUtil.cs.uid | 1 + addons/core/math/Rand.cs | 32 + addons/core/math/Rand.cs.uid | 1 + addons/core/math/Real.cs | 38 ++ addons/core/math/Real.cs.uid | 1 + addons/core/misc/Helpers.cs | 201 +++++++ addons/core/misc/Helpers.cs.uid | 1 + addons/core/misc/OLib.cs | 25 + addons/core/misc/OLib.cs.uid | 1 + addons/core/misc/POD.cs | 300 ++++++++++ addons/core/misc/POD.cs.uid | 1 + addons/core/misc/PODNode.cs | 144 +++++ addons/core/misc/PODNode.cs.uid | 1 + addons/core/misc/Samples.cs | 77 +++ addons/core/misc/Samples.cs.uid | 1 + addons/core/misc/Test.cs | 62 ++ addons/core/misc/Test.cs.uid | 1 + addons/core/misc/Util.cs | 550 ++++++++++++++++++ addons/core/misc/Util.cs.uid | 1 + addons/core/misc/VariantExt.cs | 60 ++ addons/core/misc/VariantExt.cs.uid | 1 + addons/core/misc/ser.cs | 256 ++++++++ addons/core/misc/ser.cs.uid | 1 + addons/core/mp/DirSpawner.cs | 107 ++++ addons/core/mp/DirSpawner.cs.uid | 1 + addons/core/mp/ListSpawner.cs | 26 + addons/core/mp/ListSpawner.cs.uid | 1 + addons/core/plugin.cfg | 7 + addons/core/prof/Prof.cs | 126 ++++ addons/core/prof/Prof.cs.uid | 1 + addons/core/res/DataRes.cs | 19 + addons/core/res/DataRes.cs.uid | 1 + addons/core/screens/BattleScreen.cs | 71 +++ addons/core/screens/BattleScreen.cs.uid | 1 + addons/core/screens/BattleScreenDef.cs | 22 + addons/core/screens/BattleScreenDef.cs.uid | 1 + addons/core/screens/NilScreen.cs | 29 + addons/core/screens/NilScreen.cs.uid | 1 + addons/core/screens/NilScreenDef.cs | 15 + addons/core/screens/NilScreenDef.cs.uid | 1 + addons/core/screens/Screen.cs | 20 + addons/core/screens/Screen.cs.uid | 1 + addons/core/screens/ScreenDef.cs | 23 + addons/core/screens/ScreenDef.cs.uid | 1 + addons/core/screens/ScreenGeneric.cs | 40 ++ addons/core/screens/ScreenGeneric.cs.uid | 1 + addons/core/screens/ScreenGenericDef.cs | 23 + addons/core/screens/ScreenGenericDef.cs.uid | 1 + addons/core/screens/ScreenScene3D.cs | 29 + addons/core/screens/ScreenScene3D.cs.uid | 1 + addons/core/screens/ScreneScene.cs | 30 + addons/core/screens/ScreneScene.cs.uid | 1 + addons/core/screens/SimpleScreen.cs | 34 ++ addons/core/screens/SimpleScreen.cs.uid | 1 + addons/core/screens/SimpleScreenDef.cs | 21 + addons/core/screens/SimpleScreenDef.cs.uid | 1 + addons/core/screens/TraversalScreen.cs | 49 ++ addons/core/screens/TraversalScreen.cs.uid | 1 + addons/core/screens/TraversalScreenDef.cs | 27 + addons/core/screens/TraversalScreenDef.cs.uid | 1 + addons/core/ui/ActionLine.tscn | 22 + addons/core/ui/RichTextAnim.cs | 76 +++ addons/core/ui/RichTextAnim.cs.uid | 1 + addons/core/ui/TextAnimate.cs | 243 ++++++++ addons/core/ui/TextAnimate.cs.uid | 1 + 92 files changed, 4662 insertions(+) create mode 100644 addons/core/.gitattributes create mode 100644 addons/core/.gitignore create mode 100644 addons/core/ai/AStarGraph.cs create mode 100644 addons/core/ai/AStarGraph.cs.uid create mode 100644 addons/core/ai/AStarSolver.cs create mode 100644 addons/core/ai/AStarSolver.cs.uid create mode 100644 addons/core/ai/SpherePathFollower.cs create mode 100644 addons/core/ai/SpherePathFollower.cs.uid create mode 100644 addons/core/core.cs create mode 100644 addons/core/core.cs.uid create mode 100644 addons/core/core/Game.cs create mode 100644 addons/core/core/Game.cs.uid create mode 100644 addons/core/core/Game.tscn create mode 100644 addons/core/core/MPNode.cs create mode 100644 addons/core/core/MPNode.cs.uid create mode 100644 addons/core/core/NodeSpace.cs create mode 100644 addons/core/core/NodeSpace.cs.uid create mode 100644 addons/core/fsm/FSMNode.cs create mode 100644 addons/core/fsm/FSMNode.cs.uid create mode 100644 addons/core/fsm/StateNode.cs create mode 100644 addons/core/fsm/StateNode.cs.uid create mode 100644 addons/core/math/CentEx.cs create mode 100644 addons/core/math/CentEx.cs.uid create mode 100644 addons/core/math/Geo.cs create mode 100644 addons/core/math/Geo.cs.uid create mode 100644 addons/core/math/MathUtil.cs create mode 100644 addons/core/math/MathUtil.cs.uid create mode 100644 addons/core/math/Rand.cs create mode 100644 addons/core/math/Rand.cs.uid create mode 100644 addons/core/math/Real.cs create mode 100644 addons/core/math/Real.cs.uid create mode 100644 addons/core/misc/Helpers.cs create mode 100644 addons/core/misc/Helpers.cs.uid create mode 100644 addons/core/misc/OLib.cs create mode 100644 addons/core/misc/OLib.cs.uid create mode 100644 addons/core/misc/POD.cs create mode 100644 addons/core/misc/POD.cs.uid create mode 100644 addons/core/misc/PODNode.cs create mode 100644 addons/core/misc/PODNode.cs.uid create mode 100644 addons/core/misc/Samples.cs create mode 100644 addons/core/misc/Samples.cs.uid create mode 100644 addons/core/misc/Test.cs create mode 100644 addons/core/misc/Test.cs.uid create mode 100644 addons/core/misc/Util.cs create mode 100644 addons/core/misc/Util.cs.uid create mode 100644 addons/core/misc/VariantExt.cs create mode 100644 addons/core/misc/VariantExt.cs.uid create mode 100644 addons/core/misc/ser.cs create mode 100644 addons/core/misc/ser.cs.uid create mode 100644 addons/core/mp/DirSpawner.cs create mode 100644 addons/core/mp/DirSpawner.cs.uid create mode 100644 addons/core/mp/ListSpawner.cs create mode 100644 addons/core/mp/ListSpawner.cs.uid create mode 100644 addons/core/plugin.cfg create mode 100644 addons/core/prof/Prof.cs create mode 100644 addons/core/prof/Prof.cs.uid create mode 100644 addons/core/res/DataRes.cs create mode 100644 addons/core/res/DataRes.cs.uid create mode 100644 addons/core/screens/BattleScreen.cs create mode 100644 addons/core/screens/BattleScreen.cs.uid create mode 100644 addons/core/screens/BattleScreenDef.cs create mode 100644 addons/core/screens/BattleScreenDef.cs.uid create mode 100644 addons/core/screens/NilScreen.cs create mode 100644 addons/core/screens/NilScreen.cs.uid create mode 100644 addons/core/screens/NilScreenDef.cs create mode 100644 addons/core/screens/NilScreenDef.cs.uid create mode 100644 addons/core/screens/Screen.cs create mode 100644 addons/core/screens/Screen.cs.uid create mode 100644 addons/core/screens/ScreenDef.cs create mode 100644 addons/core/screens/ScreenDef.cs.uid create mode 100644 addons/core/screens/ScreenGeneric.cs create mode 100644 addons/core/screens/ScreenGeneric.cs.uid create mode 100644 addons/core/screens/ScreenGenericDef.cs create mode 100644 addons/core/screens/ScreenGenericDef.cs.uid create mode 100644 addons/core/screens/ScreenScene3D.cs create mode 100644 addons/core/screens/ScreenScene3D.cs.uid create mode 100644 addons/core/screens/ScreneScene.cs create mode 100644 addons/core/screens/ScreneScene.cs.uid create mode 100644 addons/core/screens/SimpleScreen.cs create mode 100644 addons/core/screens/SimpleScreen.cs.uid create mode 100644 addons/core/screens/SimpleScreenDef.cs create mode 100644 addons/core/screens/SimpleScreenDef.cs.uid create mode 100644 addons/core/screens/TraversalScreen.cs create mode 100644 addons/core/screens/TraversalScreen.cs.uid create mode 100644 addons/core/screens/TraversalScreenDef.cs create mode 100644 addons/core/screens/TraversalScreenDef.cs.uid create mode 100644 addons/core/ui/ActionLine.tscn create mode 100644 addons/core/ui/RichTextAnim.cs create mode 100644 addons/core/ui/RichTextAnim.cs.uid create mode 100644 addons/core/ui/TextAnimate.cs create mode 100644 addons/core/ui/TextAnimate.cs.uid diff --git a/.gitignore b/.gitignore index bf83296..6efc746 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ export_presets.cfg data_*/ mono_crash.*.json +.DS_Store \ No newline at end of file diff --git a/addons/core/.gitattributes b/addons/core/.gitattributes new file mode 100644 index 0000000..5b3a6a3 --- /dev/null +++ b/addons/core/.gitattributes @@ -0,0 +1,285 @@ +## GITATTRIBUTES FOR WEB PROJECTS +# +# These settings are for any web project. +# +# Details per file setting: +# text These files should be normalized (i.e. convert CRLF to LF). +# binary These files are binary and should be left untouched. +# +# Note that binary is a macro for -text -diff. +###################################################################### +# Auto detect +## Handle line endings automatically for files detected as +## text and leave all files detected as binary untouched. +## This will handle all files NOT defined below. +## * text=lf +# Set the default behavior for all files. +* text=auto eol=lf + +# Godot +# Godot +# Godot +*.bin filter=lfs diff=lfs merge=lfs -text lockable +*.res filter=lfs diff=lfs merge=lfs -text lockable +*.glb filter=lfs diff=lfs merge=lfs -text lockable +*.lmbake filter=lfs diff=lfs merge=lfs -text lockable + + +# Source code +# Source code +# Source code +*.rs text +*.bash text eol=lf +*.bat text eol=crlf +*.cmd text eol=crlf +*.coffee text +*.css text diff=css +*.htm text diff=html +*.html text diff=html +*.inc text +*.ini text +*.js text +*.json text +*.jsx text +*.less text +*.ls text +*.map text -diff +*.od text +*.onlydata text +*.php text diff=php +*.pl text +*.ps1 text eol=crlf +*.py text diff=python +*.rb text diff=ruby +*.sass text +*.scm text +*.scss text diff=css +*.sh text eol=lf +.husky/* text eol=lf +*.sql text +*.styl text +*.tag text +*.ts text +*.tsx text +*.xml text +*.xhtml text diff=html + +# Docker +# Docker +# Docker +Dockerfile text + +# Documentation +# Documentation +# Documentation +*.ipynb text eol=lf +*.markdown text diff=markdown +*.md text diff=markdown +*.mdwn text diff=markdown +*.mdown text diff=markdown +*.mkd text diff=markdown +*.mkdn text diff=markdown +*.mdtxt text +*.mdtext text +*.txt text +AUTHORS text +CHANGELOG text +CHANGES text +CONTRIBUTING text +COPYING text +copyright text +*COPYRIGHT* text +INSTALL text +license text +LICENSE text +NEWS text +readme text +*README* text +TODO text + +# Templates +# Templates +# Templates +*.dot text +*.ejs text +*.erb text +*.haml text +*.handlebars text +*.hbs text +*.hbt text +*.jade text +*.latte text +*.mustache text +*.njk text +*.phtml text +*.svelte text +*.tmpl text +*.tpl text +*.twig text +*.vue text + +# Configs +# Configs +# Configs +*.cnf text +*.conf text +*.config text +.editorconfig text +.env text +.gitattributes text eol=lf +.gitconfig text +.htaccess text +*.lock text -diff +package.json text eol=lf +package-lock.json text eol=lf -diff +pnpm-lock.yaml text eol=lf -diff +.prettierrc text +yarn.lock text -diff +*.toml text +*.yaml text +*.yml text +browserslist text +Makefile text +makefile text + +# Heroku +# Heroku +# Heroku +Procfile text + +# Graphics +# Graphics +# Graphics +*.ai filter=lfs diff=lfs merge=lfs -text lockable +*.bmp filter=lfs diff=lfs merge=lfs -text lockable +*.eps filter=lfs diff=lfs merge=lfs -text lockable +*.gif filter=lfs diff=lfs merge=lfs -text lockable +*.gifv filter=lfs diff=lfs merge=lfs -text lockable +*.ico filter=lfs diff=lfs merge=lfs -text lockable +*.jng filter=lfs diff=lfs merge=lfs -text lockable +*.jp2 filter=lfs diff=lfs merge=lfs -text lockable +*.jpg filter=lfs diff=lfs merge=lfs -text lockable +*.jpeg filter=lfs diff=lfs merge=lfs -text lockable +*.jpx filter=lfs diff=lfs merge=lfs -text lockable +*.jxr filter=lfs diff=lfs merge=lfs -text lockable +*.pdf filter=lfs diff=lfs merge=lfs -text lockable +*.png filter=lfs diff=lfs merge=lfs -text lockable +*.psb filter=lfs diff=lfs merge=lfs -text lockable +*.psd filter=lfs diff=lfs merge=lfs -text lockable +# SVG treated as an asset (binary) by default. +*.svg text +# If you want to treat it as binary, +# use the following line instead. +# *.svg filter=lfs diff=lfs merge=lfs -text lockable +*.svgz filter=lfs diff=lfs merge=lfs -text lockable +*.tif filter=lfs diff=lfs merge=lfs -text lockable +*.tiff filter=lfs diff=lfs merge=lfs -text lockable +*.wbmp filter=lfs diff=lfs merge=lfs -text lockable +*.webp filter=lfs diff=lfs merge=lfs -text lockable +*.exr filter=lfs diff=lfs merge=lfs -text lockable +*.hdr filter=lfs diff=lfs merge=lfs -text lockable +*.iff filter=lfs diff=lfs merge=lfs -text lockable +*.pict filter=lfs diff=lfs merge=lfs -text lockable + + + +# 3d models +# 3d models +# 3d models +*.3dm filter=lfs diff=lfs merge=lfs -text lockable +*.3ds filter=lfs diff=lfs merge=lfs -text lockable +*.blend filter=lfs diff=lfs merge=lfs -text lockable +*.c4d filter=lfs diff=lfs merge=lfs -text lockable +*.collada filter=lfs diff=lfs merge=lfs -text lockable +*.dae filter=lfs diff=lfs merge=lfs -text lockable +*.dxf filter=lfs diff=lfs merge=lfs -text lockable +*.fbx filter=lfs diff=lfs merge=lfs -text lockable +*.jas filter=lfs diff=lfs merge=lfs -text lockable +*.lws filter=lfs diff=lfs merge=lfs -text lockable +*.lxo filter=lfs diff=lfs merge=lfs -text lockable +*.ma filter=lfs diff=lfs merge=lfs -text lockable +*.max filter=lfs diff=lfs merge=lfs -text lockable +*.mb filter=lfs diff=lfs merge=lfs -text lockable +*.obj filter=lfs diff=lfs merge=lfs -text lockable +*.ply filter=lfs diff=lfs merge=lfs -text lockable +*.skp filter=lfs diff=lfs merge=lfs -text lockable +*.stl filter=lfs diff=lfs merge=lfs -text lockable +*.ztl filter=lfs diff=lfs merge=lfs -text lockable + + +# Audio +# Audio +# Audio +*.kar filter=lfs diff=lfs merge=lfs -text lockable +*.m4a filter=lfs diff=lfs merge=lfs -text lockable +*.mid filter=lfs diff=lfs merge=lfs -text lockable +*.midi filter=lfs diff=lfs merge=lfs -text lockable +*.mp3 filter=lfs diff=lfs merge=lfs -text lockable +*.ogg filter=lfs diff=lfs merge=lfs -text lockable +*.ra filter=lfs diff=lfs merge=lfs -text lockable +*.aif filter=lfs diff=lfs merge=lfs -text lockable +*.aiff filter=lfs diff=lfs merge=lfs -text lockable +*.it filter=lfs diff=lfs merge=lfs -text lockable +*.mod filter=lfs diff=lfs merge=lfs -text lockable +*.mp3 filter=lfs diff=lfs merge=lfs -text lockable +*.ogg filter=lfs diff=lfs merge=lfs -text lockable +*.s3m filter=lfs diff=lfs merge=lfs -text lockable +*.wav filter=lfs diff=lfs merge=lfs -text lockable +*.xm filter=lfs diff=lfs merge=lfs -text lockable + +# Fonts +# Fonts +# Fonts +*.otf filter=lfs diff=lfs merge=lfs -text lockable +*.ttf filter=lfs diff=lfs merge=lfs -text lockable + +# Video +# Video +# Video +*.3gpp filter=lfs diff=lfs merge=lfs -text lockable +*.3gp filter=lfs diff=lfs merge=lfs -text lockable +*.as filter=lfs diff=lfs merge=lfs -text lockable +*.asf filter=lfs diff=lfs merge=lfs -text lockable +*.asx filter=lfs diff=lfs merge=lfs -text lockable +*.avi filter=lfs diff=lfs merge=lfs -text lockable +*.fla filter=lfs diff=lfs merge=lfs -text lockable +*.flv filter=lfs diff=lfs merge=lfs -text lockable +*.m4v filter=lfs diff=lfs merge=lfs -text lockable +*.mng filter=lfs diff=lfs merge=lfs -text lockable +*.mov filter=lfs diff=lfs merge=lfs -text lockable +*.mp4 filter=lfs diff=lfs merge=lfs -text lockable +*.mpeg filter=lfs diff=lfs merge=lfs -text lockable +*.mpg filter=lfs diff=lfs merge=lfs -text lockable +*.ogv filter=lfs diff=lfs merge=lfs -text lockable +*.swc filter=lfs diff=lfs merge=lfs -text lockable +*.swf filter=lfs diff=lfs merge=lfs -text lockable +*.webm filter=lfs diff=lfs merge=lfs -text lockable + +# Archives +# Archives +# Archives +*.7z filter=lfs diff=lfs merge=lfs -text lockable +*.gz filter=lfs diff=lfs merge=lfs -text lockable +*.jar filter=lfs diff=lfs merge=lfs -text lockable +*.rar filter=lfs diff=lfs merge=lfs -text lockable +*.tar filter=lfs diff=lfs merge=lfs -text lockable +*.zip filter=lfs diff=lfs merge=lfs -text lockable + +# Fonts +# Fonts +# Fonts +*.ttf filter=lfs diff=lfs merge=lfs -text lockable +*.eot filter=lfs diff=lfs merge=lfs -text lockable +*.otf filter=lfs diff=lfs merge=lfs -text lockable +*.woff filter=lfs diff=lfs merge=lfs -text lockable +*.woff2 filter=lfs diff=lfs merge=lfs -text lockable + +# Executables +# Executables +# Executables +*.exe filter=lfs diff=lfs merge=lfs -text lockable +*.pyc filter=lfs diff=lfs merge=lfs -text lockable +# RC files (like .babelrc or .eslintrc) +*.*rc text +# Ignore files (like .npmignore or .gitignore) +*.*ignore text diff --git a/addons/core/.gitignore b/addons/core/.gitignore new file mode 100644 index 0000000..3af6314 --- /dev/null +++ b/addons/core/.gitignore @@ -0,0 +1,67 @@ + + +# Group Ignores +ignored/ +ignore/ + + +# Personal ignores +# ignore the addons folder +addons/ +exports/ +log/ +save/ +samples/ + +# Godot 4+ specific ignores +.godot/ + +*.swp +*.*~ +project.lock.json +.DS_Store +*.pyc +nupkg/ +.vs/ + +# Visual Studio Code +# Created by https://www.gitignore.io/api/visualstudiocode +# Edit at https://www.gitignore.io/?templates=visualstudiocode + +### VisualStudioCode ### +.vscode/* # Maybe .vscode/**/* instead - see comments +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### VisualStudioCode Patch ### +# Ignore all local history of files +**/.history + +# End of https://www.gitignore.io/api/visualstudiocode + + + + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ +[Oo]ut/ +msbuild.log +msbuild.err +msbuild.wrn diff --git a/addons/core/ai/AStarGraph.cs b/addons/core/ai/AStarGraph.cs new file mode 100644 index 0000000..0865838 --- /dev/null +++ b/addons/core/ai/AStarGraph.cs @@ -0,0 +1,106 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2025 + +using Godot; +using gdcoll = Godot.Collections; +//using System.Linq; + + + + + +[Tool, GlobalClass] +public partial class AStarGraph : Resource +{ + [Export] + public gdcoll.Array Points { get; set; } = new(); + + [Export] + public gdcoll.Array> Connections { get; set; } = new(); + + private int _nextId = 0; + + public int AddPoint( Vector3 position ) + { + var id = _nextId++; + + if( Points.Count <= _nextId ) + { + Points.Resize( _nextId ); + Connections.Resize( _nextId ); + } + Points[id] = position; + Connections[id] = new gdcoll.Array(); + + return id; + } + + + + public void ConnectPoints( int idFrom, int idTo, bool bidirectional = true ) + { + //if (!Points.ContainsKey(idFrom) || !Points.ContainsKey(idTo)) return; + + //if (!Connections.ContainsKey(idFrom)) Connections[idFrom] = new(); + Connections[idFrom].Add( idTo ); + + if( bidirectional ) + { + //if (!Connections.ContainsKey(idTo)) Connections[idTo] = new(); + Connections[idTo].Add( idFrom ); + } + } + + public bool ArePointsConnected( int idFrom, int idTo ) + { + return Connections[idFrom].Contains( idTo ); + } + + public int GetAvailablePointId() => _nextId++; + + public Vector3 GetPointPosition( int id ) => Points[id]; + + public gdcoll.Array GetPointConnections( int id ) => Connections[id]; + + //public int[] GetPointIds() => Points.Keys.ToArray(); + + + public int GetClosestPoint( Vector3 position ) + { + int closestId = -1; + float closestDistSq = float.MaxValue; + + int curId = 0; + foreach( var pos in Points ) + { + if( !pos.IsFinite() ) + continue; + + float distSq = position.DistanceSquaredTo( pos ); + if( distSq < closestDistSq ) + { + if( Connections[curId].Count > 0 ) + { + closestDistSq = distSq; + closestId = curId; + } + else + { + Points[curId] = Vector3.Inf; + } + } + curId++; + } + return closestId; + } + + public void Clear() + { + Points.Clear(); + Connections.Clear(); + _nextId = 0; + } +} diff --git a/addons/core/ai/AStarGraph.cs.uid b/addons/core/ai/AStarGraph.cs.uid new file mode 100644 index 0000000..0e681d5 --- /dev/null +++ b/addons/core/ai/AStarGraph.cs.uid @@ -0,0 +1 @@ +uid://b500r6swmdc3w diff --git a/addons/core/ai/AStarSolver.cs b/addons/core/ai/AStarSolver.cs new file mode 100644 index 0000000..b4f00e1 --- /dev/null +++ b/addons/core/ai/AStarSolver.cs @@ -0,0 +1,79 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2025 + +using Godot; +using System.Collections.Generic; + +public static class AStarSolver +{ + public static Vector3[] FindPath( AStarGraph graph, Vector3 startPos, Vector3 endPos ) + { + int startId = graph.GetClosestPoint( startPos ); + int endId = graph.GetClosestPoint( endPos ); + + log.info( $"Finding path from {startPos.Log}->{endPos.Log} ({startId}->{endId})" ); + + if( startId == -1 || endId == -1 ) + return new Vector3[0]; + + var openSet = new PriorityQueue(); + var cameFrom = new Dictionary(); + + var gScore = new Dictionary(); + gScore[startId] = 0; + + var fScore = new Dictionary(); + fScore[startId] = startPos.DistanceTo( endPos ); + + openSet.Enqueue( startId, fScore[startId] ); + + while( openSet.Count > 0 ) + { + int currentId = openSet.Dequeue(); + if( currentId == endId ) + { + log.debug( $"Found path!" ); + return ReconstructPath( startPos, endPos, cameFrom, currentId, graph ); + } + + foreach( int neighborId in graph.GetPointConnections( currentId ) ) + { + Vector3 currentPos = graph.GetPointPosition( currentId ); + Vector3 neighborPos = graph.GetPointPosition( neighborId ); + float tentativeGScore = gScore.GetValueOrDefault( currentId, float.MaxValue ) + currentPos.DistanceTo( neighborPos ); + + if( tentativeGScore < gScore.GetValueOrDefault( neighborId, float.MaxValue ) ) + { + cameFrom[neighborId] = currentId; + gScore[neighborId] = tentativeGScore; + fScore[neighborId] = tentativeGScore + neighborPos.DistanceTo( endPos ); + + openSet.Enqueue( neighborId, fScore[neighborId] ); + } + } + } + + return new Vector3[0]; // No path found + } + + private static Vector3[] ReconstructPath( Vector3 startPos, Vector3 endPos, Dictionary cameFrom, int currentId, AStarGraph graph ) + { + var path = new List { endPos, graph.GetPointPosition( currentId ) }; + while( cameFrom.ContainsKey( currentId ) ) + { + currentId = cameFrom[currentId]; + path.Add( graph.GetPointPosition( currentId ) ); + } + path.Add( startPos ); + + path.Reverse(); + + log.debug( $"Path is {path.Count} long." ); + + + return path.ToArray(); + } +} diff --git a/addons/core/ai/AStarSolver.cs.uid b/addons/core/ai/AStarSolver.cs.uid new file mode 100644 index 0000000..4391588 --- /dev/null +++ b/addons/core/ai/AStarSolver.cs.uid @@ -0,0 +1 @@ +uid://c7e80my8xas6l diff --git a/addons/core/ai/SpherePathFollower.cs b/addons/core/ai/SpherePathFollower.cs new file mode 100644 index 0000000..8551a41 --- /dev/null +++ b/addons/core/ai/SpherePathFollower.cs @@ -0,0 +1,175 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2025 + +using Godot; + +/// +/// A path follower that treats the agent as a sphere. The target point is the +/// intersection of this sphere with the current path segment. +/// +public class SpherePathFollower +{ + private Vector3[] _path; + private float _radius = 4.0f; + private int _currentIndex = 0; + + public Vector3[] Path => _path; + + public int Index => _currentIndex; + + public int MaxIndex => _path.Length; + + /// + /// Checks if the agent has reached the end of the path. + /// + public bool IsFinished => _currentIndex >= _path.Length; + + /// + /// Checks if the agent has reached the end of the path. + /// + public bool IsNearlyFinished => _currentIndex >= ( _path.Length - 1 ); + + /// + /// Initializes a new path follower with a path and a sphere radius. + /// + /// The array of Vector3 points to follow. + /// The radius of the agent's collision/interaction sphere. + public SpherePathFollower( float sphereRadius ) + { + _radius = sphereRadius; + SetPath( new Vector3[0], sphereRadius ); + } + + /// + /// Initializes a new path follower with a path and a sphere radius. + /// + /// The array of Vector3 points to follow. + /// The radius of the agent's collision/interaction sphere. + public SpherePathFollower( Vector3[] path, float sphereRadius ) + { + SetPath( path, sphereRadius ); + } + + /// + /// Assigns a new path and radius, resetting the follower's progress. + /// + public void SetPath( Vector3[] newPath ) + { + SetPath( newPath, _radius ); + } + + /// + /// Assigns a new path and radius, resetting the follower's progress. + /// + public void SetPath( Vector3[] newPath, float sphereRadius ) + { + _path = newPath ?? new Vector3[0]; + _radius = Mathf.Max( 0.01f, sphereRadius ); // Ensure radius is positive + _currentIndex = 0; + } + + /// + /// Calculates the next target point on the current path segment for the agent to move towards. + /// + /// The current world position of the agent. + /// The world-space target point on the path segment. + public Vector3 GetTargetPoint( Vector3 agentPos, Vector3 goalPos, out bool advance ) + { + advance = false; + if( IsNearlyFinished ) + { + return goalPos; + } + + Vector3 p1 = _path[_currentIndex]; + Vector3 p2 = _path[_currentIndex + 1]; + + //DebugDraw3D.DrawLine(p1, p2, Colors.Blue); + + Vector3 intersects = p2; + + var intersectLine = Geo.SphereSegment( agentPos, _radius, p1, p2, out intersects, out var intersectType ); + + /* + if (!intersectLine) + { + intersects = p1; + intersectType = Geo.IntersectType.Before; + } + */ + + //log.info($"{log.loc().Log}: Seg:({p1.Log})->({p2.Log}) On {intersectLine} Type {intersectType} To {intersects.Log}"); + + switch( intersectType ) + { + case Geo.IntersectType.After: + advance = true; + break; + } + + return intersects; + } + + + /// + /// Updates the follower's progress along the path. Call this after the agent moves. + /// + /// The agent's new world position after moving. + /// True if updated + public int Update( Vector3 agentPos, Vector3 goalPos, bool advance ) + { + if( IsNearlyFinished ) + { + if( !IsFinished ) + return ++_currentIndex; + + return _currentIndex; + } + + Vector3 endOfSegment = _path[_currentIndex + 1]; + + // Check if the agent's sphere has reached the end of the current segment + if( advance || agentPos.DistanceSquaredTo( endOfSegment ) <= _radius * _radius ) + { + ++_currentIndex; + } + + var targetPos = GetTargetPoint( agentPos, goalPos, out advance ); + + return _currentIndex; + } + + + /// + /// Updates the follower's progress along the path. Call this after the agent moves. + /// + /// The agent's new world position after moving. + /// True if updated + public int Update_orig( Vector3 agentPosition, out bool advance ) + { + advance = false; + + if( IsFinished ) + return _currentIndex; + + Vector3 endOfSegment = _path[_currentIndex + 1]; + + // Check if the agent's sphere has reached the end of the current segment + while( agentPosition.DistanceTo( endOfSegment ) <= _radius ) + { + _currentIndex++; + + advance = true; + + if( IsFinished ) + return _currentIndex; + + endOfSegment = _path[_currentIndex + 1]; + } + + return _currentIndex; + } +} diff --git a/addons/core/ai/SpherePathFollower.cs.uid b/addons/core/ai/SpherePathFollower.cs.uid new file mode 100644 index 0000000..e9a8880 --- /dev/null +++ b/addons/core/ai/SpherePathFollower.cs.uid @@ -0,0 +1 @@ +uid://dlfmqjpqpduxn diff --git a/addons/core/core.cs b/addons/core/core.cs new file mode 100644 index 0000000..994b674 --- /dev/null +++ b/addons/core/core.cs @@ -0,0 +1,219 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2024 + +#if TOOLS +using Godot; +using Godot.NativeInterop; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using System; +using System.Diagnostics; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.Loader; +using System.Text.Json; + +//Currently doesnt _quiiiite_ work + + +[GlobalClass, Tool] +public partial class core : EditorPlugin +{ + + +#if DONT_RUN + + public static void SetupUnloads() + { + GD.Print($"SetupUnloads"); + + //AppDomain.CurrentDomain.FirstChanceException +=CurrentDomainOnFirstChanceException; + + var assContext = System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(System.Reflection.Assembly.GetExecutingAssembly()); + + assContext.Unloading += alc => + { + GD.Print($"Unloading (from Initialize)"); + + //Debug.WriteLine("Unloading (from Initialize)"); + + { + ////complete godot async tasks, which may have been left hanging + var isDone = false; + while (isDone is false) + { + try + { + Dispatcher.SynchronizationContext.ExecutePendingContinuations(); + isDone = true; + GD.Print("Godot async tasks completed."); + } + catch (Exception ex) + { + Debug.WriteLine("Exception: " + ex); + /*if (ex._IsRoutineControlFlow() is false) + { + __.Assert(ex); + }*/ + } + } + } + + + //need to detach the handler, or it will keep the assembly alive. + //AppDomain.CurrentDomain.FirstChanceException -= CurrentDomainOnFirstChanceException; + + { + //unload STJ cached assemblies, + //see https://github.com/dotnet/runtime/issues/65323#issuecomment-1320949911 + //and https://github.com/godotengine/godot/issues/78513#issuecomment-1624682104 + GD.Print($"Clearing STJ cache"); + var assembly = typeof(JsonSerializerOptions).Assembly; + var updateHandlerType = assembly.GetType("System.Text.Json.JsonSerializerOptionsUpdateHandler"); + var clearCacheMethod = updateHandlerType?.GetMethod("ClearCache", BindingFlags.Static | BindingFlags.Public); + clearCacheMethod?.Invoke(null, new object?[] { null }); + GD.Print($"Cleared STJ cache"); + + } + }; + } + + + public override void _EnterTree() + { + GD.Print($"_EnterTree"); + var assContext = System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(System.Reflection.Assembly.GetExecutingAssembly()); + + assContext.Unloading += OnUnloading; + + try + { + //res.Mgr.startup(); + lib.Config.startup("editor.xml"); + ent.Util.RegisterData(); + Util.SetupSaveState(); + + GD.Print($"DONE Setting up core."); + } + catch (Exception ex) + { + GD.PrintErr($"Ruh roh, ex {ex.GetType().Name} {ex.Message}"); + } + + } + + + + + + [ModuleInitializer] + public static void OnLoading() + { + GD.Print($"OnLoading"); + + SetupUnloads(); + SetupCore(); + } + + private void OnUnloading(AssemblyLoadContext context) + { + GD.Print($"OnUnloading"); + + Game.shutdownLogging(); + } + + public override void _ExitTree() + { + GD.Print($"_ExitTree"); + } + + + public static void SetupCore() + { + var id = Process.GetCurrentProcess().Id; + + GD.Print($"Setting up core..."); + + // TWICE (down below) + Game.setupLogging($"editor", $""); + + // Initialization of the plugin goes here. + log.high($"Starting up core..."); + + var windowTitle = $"D E R E L I C T ({id})"; + + log.high(windowTitle); + } +#endif + + +#if DONT_RUN + public override void _EnterTree() + { + var id = Process.GetCurrentProcess().Id; + + GD.Print($"Setting up core..."); + + // TWICE (down below) + Game.setupLogging($"editor", $""); + + // Initialization of the plugin goes here. + log.high($"Starting up core..."); + + var windowTitle = $"D E R E L I C T ({id})"; + + log.high(windowTitle); + try + { + //res.Mgr.startup(); + lib.Config.startup("editor.xml"); + ent.Util.RegisterData(); + Util.SetupSaveState(); + + var curAss = Assembly.GetExecutingAssembly(); + AssemblyLoadContext.GetLoadContext(curAss).Unloading += OnUnloading; + + GD.Print($"DONE Setting up core."); + } + catch (Exception ex) + { + GD.PrintErr($"Ruh roh, ex {ex.GetType().Name} {ex.Message}"); + } + } + + [ModuleInitializer] + static public void OnLoading() + { + GD.Print($"On LOADing: Unpause thread"); + //log.unpauseThread(); + Game.setupLogging($"editor", $""); + } + + private void OnUnloading(AssemblyLoadContext context) + { + GD.Print($"On UNloading: Pause thread"); + + //log.pauseThread(); + Game.shutdownLogging(); + } + + public override void _ExitTree() + { + // Clean-up of the plugin goes here. + log.high($"Shutting down core . . ."); + + Game.shutdownLogging(); + + + + + + log.high($"D O N E Shutting down core."); + } +#endif + +} +#endif diff --git a/addons/core/core.cs.uid b/addons/core/core.cs.uid new file mode 100644 index 0000000..53dc357 --- /dev/null +++ b/addons/core/core.cs.uid @@ -0,0 +1 @@ +uid://t5s8xt1qqpoq diff --git a/addons/core/core/Game.cs b/addons/core/core/Game.cs new file mode 100644 index 0000000..12238e6 --- /dev/null +++ b/addons/core/core/Game.cs @@ -0,0 +1,391 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2024 +using System; +using System.Diagnostics; +using System.IO; +using System.Linq.Expressions; +using System.Net.NetworkInformation; +using System.Runtime.CompilerServices; +using Godot; + +using Godot.Sharp.Extras; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using prof; + + +[GlobalClass] +public partial class Game : CanvasLayer +{ + static public Game _ { get; private set; } + + + [Prefix( "AR" )] + public Container Display; + public Node DisplayNode; + public string _reason = ""; + + + private Node _test; + + /* + // @@@@ This isnt great. Really need to refactor + // @@@@ This isnt great. Really need to refactor + // @@@@ This isnt great. Really need to refactor + [Prefix("../game")] + public PackedScene MainMenu; + + [Prefix("../game")] + public PackedScene Gameplay; + */ + + //private Button XButton; + + private Label FPSLabel; + + #region Logging + + static string s_extraHeader = $""; + + + static public void PrintToGDPrint( log.LogEvent evt ) + { + var color = ""; + var bold = false; + + switch( evt.LogType ) + { + case log.LogType.Invalid: + color = "yellow"; + break; + + case log.LogType.Trace: + color = "grey"; + break; + + case log.LogType.Debug: + color = "grey"; + break; + + case log.LogType.Info: + color = "white"; + break; + + case log.LogType.High: + color = "white"; + bold = true; + break; + + case log.LogType.Warn: + color = "yellow"; + break; + + case log.LogType.Error: + color = "red"; + bold = true; + break; + + case log.LogType.Fatal: + color = "red"; + bold = true; + break; + } + + var msgHeader = log.headerPrint( evt ); + + var msg = $"{( bold ? "[b]" : "" )}[color={color}]{evt.Msg}[/color]{( bold ? "[/b]" : "" )}"; + + // @@ PERF PrintRich is quite slow and uses .replace to parse (as it were) the bbcode (ie html with [] tags) + GD.PrintRich( $"{s_extraHeader,10}| {msgHeader}{msg}" ); + + /* + if( evt.LogType == log.LogType.Warn ) + { + GD.PushWarning( $"| {evt.Msg}", evt.Path, evt.Line, evt.Member ); + } + + if( evt.LogType == log.LogType.Error ) + { + GD.PushError( $"| {evt.Msg}" ); + } + */ + } + + + public void SetTitle( string details ) + { + var id = Process.GetCurrentProcess().Id; + var windowTitle = $"D E R E L I C T ({id}) Scene[{details}]"; + DisplayServer.WindowSetTitle( windowTitle ); + } + + static public string s_logDir = "log/"; + + static public void setupLogging( string prefix, string suffix ) + { + if( log.IsLogging ) + { + log.warn( $"Logging already started, cannot start again" ); + return; + } + + var logFilename = LogFilename( prefix, suffix ); + + /* + var file = new FileInfo( $"{logDir}{logFilename}" ); + if( file.Exists ) + { + if( file.Length > 10 * 1024 * 1024 ) + { + logFilename = $"{logFilePrefix}_{now.Hour:00}.log"; + } + } + //*/ + + // Setup .warn and .error so that they are directly sent to GDPrint + // This makes it so the real causes are in the stack trace in Godot and can be clicked + //log.addDirectCallback( log.LogType.Warn, PrintWarning ); + + //log.addDirectCallback( log.LogType.Error, PrintError ); + + GD.Print( $"***** Starting {prefix} logging system for file {logFilename}" ); + + + + s_extraHeader = suffix; + + //log.create( $"{logDir}{logFilename}", log.Endpoints.File ); + log.startup( $"{s_logDir}{logFilename}", log.Endpoints.File ); + + log.addDelegate( PrintToGDPrint ); + + + log.high( $"Machine and C# Runtime Environment" ); + log.var( System.Environment.ProcessId ); + + log.var( System.Environment.CommandLine ); + + log.var( System.Environment.CurrentDirectory ); + log.var( System.Environment.SystemDirectory ); + + log.var( System.Environment.GetEnvironmentVariable( "PATH" ) ); + log.var( System.Environment.GetEnvironmentVariable( "USER" ) ); + log.var( System.Environment.GetEnvironmentVariable( "HOME" ) ); + + log.var( System.Environment.ProcessorCount ); + log.var( System.Environment.Is64BitOperatingSystem ); + log.var( System.Environment.Is64BitProcess ); + + log.var( System.Environment.MachineName ); + + log.var( System.Environment.Version ); + log.var( System.Environment.OSVersion ); + + + GD.Print( $"* End setupLogging" ); + + } + + public static string LogFilename( string prefix, string suffix ) + { + var now = DateTime.Now; + + //var logFilePrefix = $"{prefix}_{now.Year}{now.Month:00}{now.Day:00}_{suffix}"; + var logFilePrefix = $"{prefix}_{now.Year}{now.Month:00}{now.Day:00}{( suffix.NOW() ? "" : "_" )}{suffix}"; + + //New log file every 10 minutes; + //var logFilename = $"{logFilePrefix}_{now.Hour:00}_{now.Minute / 10:0}0.log"; + var logFilename = $"{logFilePrefix}.log"; + return logFilename; + } + + static public void shutdownLogging() + { + log.high( $"Shutting down logging" ); + log.shutdown(); + } + + [StackTraceHidden] + private static void PrintError( log.LogEvent le ) + { + PrintToGDPrint( le ); + GD.PushError( $"| {le.Msg}" ); + } + + [StackTraceHidden] + private static void PrintWarning( log.LogEvent le ) + { + PrintToGDPrint( le ); + GD.PushWarning( $"| {le.Msg}", le.Path, le.Line, le.Member ); + } + + static public void LogCallable( Variant dictVar ) + { + var dict = dictVar.AsGodotDictionary(); + + var msg = dict["text"].AsString(); + + if( msg[0] == '|' ) + { + return; + } + + // The dictionary that comes in has almost nothing. Currently Ive only seen text: the message and type: the log type + // It has no other keys. + log.info( $"{msg}", cat: "engine" ); + } + + #endregion Logging + + public Game() + { + _ = this; + + //log.trace( $"Notification: Game()" ); + } + + public override void _Notification( int note ) + { + base._Notification( note ); + //log.trace( $"Notification: {note}" ); + } + + public override void _EnterTree() + { + //log.trace( $"Notification: _EnterTree" ); + } + + + public override void _Ready() + { + + this.OnReady(); + + + // Only half useful + //var callable = Callable.From( ( Variant v ) => LogCallable( v ) ); + //Godot.LogManager.RegisterLogCaptureNonThreadSafe( callable ); + + + //this.OnReady(); + + /* + log.trace( $"Trace" ); + log.debug( $"Debug" ); + log.info ( $"Info" ); + log.high ( $"High" ); + log.warn ( $"Warn" ); + log.error( $"Error" ); + /*/ + log.high( $"Skipping log demo" ); + // */ + + //log.fatal( $"Fatal" ); + + + Tools.ReadyType( this ); + + + //Util.SetupSaveState(); + + if( DisplayNode != null && string.IsNullOrEmpty( DisplayNode.Name ) && DisplayNode.NamePath != null ) + { + log.info( $"Adding DisplayNode:{DisplayNode.Name} SceneFilePath: {DisplayNode.SceneFilePath} to Display" ); + Display.AddChild( DisplayNode ); + } + else + { + log.warn( $"DisplayNode is null, not adding to Display" ); + } + + log.high( $"**** Game scene loaded" ); + } + + override public void _Process( double dt ) + { + using var zone = new prof.Zone(); + + var fpsF = 1.0 / dt; + + int fps = (int)fpsF; + + FPSLabel.Text = $"FPS: {fps}"; + + } + + + public void SwitchToScene( PackedScene scene, string reason, + [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "", [CallerArgumentExpression( "scene" )] string dbgExpScene = "" ) + { + if( scene == null ) + { + log.warn( $"Cannot switch to null scene {reason}" ); + return; + } + + log.info( $"Switching to {scene.ResourcePath}" ); + + var node = scene.Instantiate(); + //node.Name = $"{log.whatFile( dbgPath )}_{log.whatFile( scene.ResourcePath )}"; + + var newReason = $"{reason} ({log.whatFile( scene.ResourcePath )})"; + + var titleDetails = $"{node.Name}"; + SetTitle( titleDetails ); + + SwitchTo( node, reason, dbgPath, dbgLine, dbgMethod, $"LoadScene({dbgExpScene})", dbgCallFromScene: true ); + } + + public void SwitchTo( Node sceneNode, string reason, + [CallerFilePath] string dbgPath = "", [CallerLineNumber] int dbgLine = -1, [CallerMemberName] string dbgMethod = "", [CallerArgumentExpression( "sceneNode" )] string dbgExpSceneNode = "", bool dbgCallFromScene = false ) + { + try + { + if( sceneNode == null ) + { + log.warn( $"Cannot switch to null scene {reason}" ); + return; + } + + log.info( $"Switching to {sceneNode.Name} bcs {reason} from {log.relativePath( dbgPath )}:({dbgLine}): {dbgMethod}" ); + + DisplayNode?.QueueFree(); + foreach( var child in Display.GetChildren() ) + { + child.QueueFree(); + Display.RemoveChild( child ); + } + DisplayNode = sceneNode; + Display.AddChild( DisplayNode ); + + if( !dbgCallFromScene ) + { + var titleDetails = $"{log.whatFile( dbgPath )}.{dbgMethod}"; + SetTitle( titleDetails ); + } + + _reason = reason; + } + catch( Exception ex ) + { + log.warn( $"Caught ex {ex.Message} while trying to go to {sceneNode.Name} from res://{log.relativePath( dbgPath )}:({dbgLine}): {dbgMethod}" ); + } + } + + /* + private void XButton_Pressed() + { + //SwitchTo( MainMenu ); + } + */ + +} + + + +/// // +/// diff --git a/addons/core/core/Game.cs.uid b/addons/core/core/Game.cs.uid new file mode 100644 index 0000000..3c85d79 --- /dev/null +++ b/addons/core/core/Game.cs.uid @@ -0,0 +1 @@ +uid://c4g565oftdnax diff --git a/addons/core/core/Game.tscn b/addons/core/core/Game.tscn new file mode 100644 index 0000000..411dc05 --- /dev/null +++ b/addons/core/core/Game.tscn @@ -0,0 +1,38 @@ +[gd_scene load_steps=2 format=3 uid="uid://b22oq70jqfh2o"] + +[ext_resource type="Script" path="res://addons/core/core/Game.cs" id="1_2r7cu"] + +[node name="Game" type="CanvasLayer"] +layer = 0 +script = ExtResource("1_2r7cu") + +[node name="AR" type="AspectRatioContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -200.0 +offset_right = 200.0 +grow_horizontal = 2 +grow_vertical = 2 + +[node name="Display" type="PanelContainer" parent="AR"] +custom_minimum_size = Vector2(3200, 2.08165e-12) +layout_mode = 2 + +[node name="DEBUGWarning" type="Label" parent="AR/Display"] +layout_mode = 2 +size_flags_horizontal = 4 +text = "DEFAULT SCENE + +Something Broke! This shouldnt be here" + +[node name="FPSLabel" type="Label" parent="."] +offset_left = 50.0 +offset_top = 39.0 +offset_right = 86.0 +offset_bottom = 62.0 +theme_override_colors/font_color = Color(1, 1, 1, 0.254902) +theme_override_font_sizes/font_size = 30 +text = "FPS: " + +[connection signal="visibility_changed" from="." to="." method="_on_visibility_changed"] diff --git a/addons/core/core/MPNode.cs b/addons/core/core/MPNode.cs new file mode 100644 index 0000000..c64b0a3 --- /dev/null +++ b/addons/core/core/MPNode.cs @@ -0,0 +1,28 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2025 + +using Godot; +using System; + +[GlobalClass] +public partial class MPNode : Node3D +{ + + + + + virtual public string GetNameFromId( long id ) => $"{id}"; + + // Called when the node enters the scene tree for the first time. + public override void _Ready() + { + } + + // Called every frame. 'delta' is the elapsed time since the previous frame. + public override void _Process( double delta ) + { + } +} diff --git a/addons/core/core/MPNode.cs.uid b/addons/core/core/MPNode.cs.uid new file mode 100644 index 0000000..a80b313 --- /dev/null +++ b/addons/core/core/MPNode.cs.uid @@ -0,0 +1 @@ +uid://dykdxaolk0qpa diff --git a/addons/core/core/NodeSpace.cs b/addons/core/core/NodeSpace.cs new file mode 100644 index 0000000..b6acca5 --- /dev/null +++ b/addons/core/core/NodeSpace.cs @@ -0,0 +1,52 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2025 + +using Godot; +using System; + +[GlobalClass] +public partial class NodeSpace : Node +{ + + [Export] + public string NodeClass; + + + public void ReplaceByScene( PackedScene scene ) + { + var node = scene.Instantiate(); + node.Name = Name; + + log.info( $"Replace {NodeClass} with {node.GetType().Name}" ); + + ReplaceBy( node ); + + QueueFree(); + } + + public void ReplaceByScene( string scenePath ) + { + log.info( $"Replacing {Name} by {scenePath} (at {GetPath()})" ); + var scene = ResourceLoader.Load( scenePath ); + + if( scene != null ) + ReplaceByScene( scene ); + else + { + log.warn( $"Could not load {scenePath}" ); + } + } + + // Called when the node enters the scene tree for the first time. + public override void _Ready() + { + } + + // Called every frame. 'delta' is the elapsed time since the previous frame. + public override void _Process( double delta ) + { + } +} diff --git a/addons/core/core/NodeSpace.cs.uid b/addons/core/core/NodeSpace.cs.uid new file mode 100644 index 0000000..db908ab --- /dev/null +++ b/addons/core/core/NodeSpace.cs.uid @@ -0,0 +1 @@ +uid://d3uvyevm1600t diff --git a/addons/core/fsm/FSMNode.cs b/addons/core/fsm/FSMNode.cs new file mode 100644 index 0000000..b449be2 --- /dev/null +++ b/addons/core/fsm/FSMNode.cs @@ -0,0 +1,86 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2024 +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Godot; +using Godot.Sharp.Extras; + +namespace fsm; + +[GlobalClass] +public partial class FSMNode : Node +{ + + [ExportCategory( "Config" )] + [Export] + public NodePath InitialNodePath = "Movement"; + + + [ExportCategory( "Runtime" )] + [Export] + public StateNode Current = new StateNode(); + + [Export] + public NodePath _nextNode; + + + + + public virtual void Transition( NodePath nextNode ) + { + if( nextNode == _nextNode ) + return; + + if( !_nextNode.IsEmpty ) + { + log.warn( $"{log.loc().Log}: Was going to {_nextNode} but instead going to {nextNode}" ); + } + + _nextNode = nextNode; + } + + + + + public override void _Ready() + { + base._Ready(); + this.OnReady(); + + Current.Name = "NOPState"; + + _nextNode = InitialNodePath; + } + + + public override void _PhysicsProcess( double dt ) + { + base._PhysicsProcess( dt ); + + //Do the transition + if( !_nextNode.IsEmpty ) + { + var nextNode = GetNodeOrNull( _nextNode ); + + var oldNodePath = new NodePath( Current.Name ); + + Current.OnExit( _nextNode ); + + Current.ProcessMode = Node.ProcessModeEnum.Disabled; + + Current = nextNode; + + Current.OnEnter( oldNodePath ); + + Current.ProcessMode = Node.ProcessModeEnum.Pausable; + } + + } + +} + diff --git a/addons/core/fsm/FSMNode.cs.uid b/addons/core/fsm/FSMNode.cs.uid new file mode 100644 index 0000000..b5990fd --- /dev/null +++ b/addons/core/fsm/FSMNode.cs.uid @@ -0,0 +1 @@ +uid://cwq218bv6mw6x diff --git a/addons/core/fsm/StateNode.cs b/addons/core/fsm/StateNode.cs new file mode 100644 index 0000000..8bebb8e --- /dev/null +++ b/addons/core/fsm/StateNode.cs @@ -0,0 +1,47 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2024 +using System; +using Godot; +using Godot.Sharp.Extras; + +namespace fsm; + + +[GlobalClass] +public partial class StateNode : Node +{ + + [Export] + public bool _moveAndRotate = true; + + public virtual void OnEnter( NodePath oldNode ) + { + + } + + public virtual void OnExit( NodePath newNode ) + { + + } + + public override void _Ready() + { + base._Ready(); + this.OnReady(); + + + } + + public override void _PhysicsProcess( double dt ) + { + base._PhysicsProcess( dt ); + + + } + + +} + diff --git a/addons/core/fsm/StateNode.cs.uid b/addons/core/fsm/StateNode.cs.uid new file mode 100644 index 0000000..8a638b9 --- /dev/null +++ b/addons/core/fsm/StateNode.cs.uid @@ -0,0 +1 @@ +uid://bmi8t3kreo03t diff --git a/addons/core/math/CentEx.cs b/addons/core/math/CentEx.cs new file mode 100644 index 0000000..07214c2 --- /dev/null +++ b/addons/core/math/CentEx.cs @@ -0,0 +1,22 @@ + + + + + +static public class Vector3CmExt +{ + + public static Vector3Cm ToVector3Cm( this Godot.Vector3 vec ) => new Vector3Cm( vec.X.Cm, vec.Y.Cm, vec.Z.Cm ); + + + public static Godot.Vector3 ToVector3( this Vector3Cm vecCm ) + { + var xF = vecCm.x.Float; + var yF = vecCm.y.Float; + var zF = vecCm.z.Float; + + return new Godot.Vector3( xF, yF, zF ); + } + + +} diff --git a/addons/core/math/CentEx.cs.uid b/addons/core/math/CentEx.cs.uid new file mode 100644 index 0000000..14a0008 --- /dev/null +++ b/addons/core/math/CentEx.cs.uid @@ -0,0 +1 @@ +uid://c1cqu7yvvsd75 diff --git a/addons/core/math/Geo.cs b/addons/core/math/Geo.cs new file mode 100644 index 0000000..58ef8d5 --- /dev/null +++ b/addons/core/math/Geo.cs @@ -0,0 +1,152 @@ +using Godot; + +public static class Geo +{ + + public enum IntersectType + { + Before = -1, // Intersection with the infinite line occurs before p1 + In = 0, // Intersection occurs between p1 and p2 + After = 1, // Intersection with the infinite line occurs after p2 + Short = 2 // The sphere is already past the segment's endpoint + }; + + /// + /// Calculates the intersection of a sphere with a line segment, always returning a point on the segment. + /// + /// The world position of the sphere's center. + /// The radius of the sphere. + /// The starting point of the line segment. + /// The ending point of the line segment. + /// The resulting intersection point, clamped to the segment. + /// The type of intersection that occurred. + /// True if an intersection with the infinite line is mathematically possible, false otherwise. + public static bool SphereSegment( + Vector3 sphereCenter, + float sphereRadius, + Vector3 segmentP1, + Vector3 segmentP2, + out Vector3 intersectionPoint, + out IntersectType intersectionType ) + { + Vector3 segmentVector = segmentP2 - segmentP1; + float segmentLengthSq = segmentVector.LengthSquared(); + + if( segmentLengthSq < 0.0001f ) + { + intersectionPoint = segmentP2; + intersectionType = IntersectType.Short; + return false; + } + + // Project sphereCenter onto the line defined by the segment + float t = ( sphereCenter - segmentP1 ).Dot( segmentVector ) / segmentLengthSq; + Vector3 closestPointOnLine = segmentP1 + t * segmentVector; + + float distSq = sphereCenter.DistanceSquaredTo( closestPointOnLine ); + if( distSq > sphereRadius * sphereRadius ) + { + // No intersection with the infinite line, so return the closest point on the segment. + intersectionPoint = closestPointOnLine; //.Clamp(segmentP1, segmentP2); + intersectionType = t < 0 ? IntersectType.Before : IntersectType.After; + return false; + } + + // Calculate the intersection point on the infinite line + float offset = Mathf.Sqrt( sphereRadius * sphereRadius - distSq ); + Vector3 idealIntersection = closestPointOnLine + segmentVector.Normalized() * offset; + + // Determine the intersection type based on the ideal point's position + float finalT = ( idealIntersection - segmentP1 ).Dot( segmentVector ) / segmentLengthSq; + + if( finalT < 0 ) + { + intersectionType = IntersectType.Before; + } + else if( finalT > 1.0f ) + { + intersectionType = IntersectType.After; + } + else + { + intersectionType = IntersectType.In; + } + + // Always return a point clamped to the segment + intersectionPoint = idealIntersection; //.Clamp(segmentP1, segmentP2); + return true; + } + + + /// + /// Calculates the intersection of a sphere and a line segment. + /// + /// The world position of the sphere's center. + /// The radius of the sphere. + /// The starting point of the line segment. + /// The ending point of the line segment. + /// The point where the sphere first intersects the infinite line. + /// Indicates where the intersection lies: -1 for before the segment, 0 for on the segment, 1 for after the segment. + /// True if an intersection with the infinite line exists, false otherwise. + public static bool SphereSegment_old( + Vector3 sphereCenter, + float sphereRadius, + Vector3 segmentP1, + Vector3 segmentP2, + out Vector3 intersectionPoint, + out IntersectType intersectionType ) + { + // Initialize out parameters + //intersectionPoint = Vector3.Zero; + intersectionType = IntersectType.Before; + + Vector3 segmentVector = segmentP2 - segmentP1; + float segmentLength = segmentVector.Length(); + + // Handle zero-length segment case + if( segmentLength < 0.0001f ) + { + intersectionType = IntersectType.Short; + intersectionPoint = segmentP2; + return false; + } + + Vector3 lineDir = segmentVector / segmentLength; + + // 1. Find the closest point on the infinite line to the sphere's center + var toCenter = sphereCenter - segmentP1; + float t = toCenter.Dot( lineDir ); + var closestPointOnLine = segmentP1 + lineDir * t; + + bool isOnLine = true; + + // 2. Check if an intersection is possible + float distSq = sphereCenter.DistanceSquaredTo( closestPointOnLine ); + if( distSq > sphereRadius * sphereRadius ) + { + isOnLine = false; + } + + // 3. Calculate the intersection point + float offset = Mathf.Sqrt( sphereRadius * sphereRadius - distSq ); + intersectionPoint = closestPointOnLine - lineDir * offset; + + // 4. Determine where the intersection lies relative to the segment + float intersectionT = ( intersectionPoint - segmentP1 ).Dot( lineDir ); + + if( intersectionT < 0 ) + { + intersectionType = IntersectType.Before; // Before the segment + } + else if( intersectionT > segmentLength ) + { + intersectionType = IntersectType.After; // After the segment + } + else + { + intersectionType = IntersectType.In; // On the segment + } + + return isOnLine; + } +} diff --git a/addons/core/math/Geo.cs.uid b/addons/core/math/Geo.cs.uid new file mode 100644 index 0000000..b0143d7 --- /dev/null +++ b/addons/core/math/Geo.cs.uid @@ -0,0 +1 @@ +uid://bsqypnus3htcr diff --git a/addons/core/math/MathUtil.cs b/addons/core/math/MathUtil.cs new file mode 100644 index 0000000..6265f40 --- /dev/null +++ b/addons/core/math/MathUtil.cs @@ -0,0 +1,67 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2025 + +using Godot; +using Godot.Sharp.Extras; +using System; + + + + +static public class MathExtensions +{ + + + static public int Range( this Random rand, int top ) + { + return rand.Range( 0, top ); + } + static public float Range( this Random rand, float top ) + { + return rand.Range( 0.0f, top ); + } + + static public double Range( this Random rand, double top ) + { + return rand.Range( 0.0, top ); + } + + static public int Range( this Random rand, int bot, int top ) + { + var range = top - bot; + var r = ( rand.Next() % range ) + bot; + return r; + } + + static public float Range( this Random rand, float bot, float top ) + { + var range = top - bot; + var r = ( rand.NextSingle() * range ) + bot; + return r; + } + + static public double Range( this Random rand, double bot, double top ) + { + var range = top - bot; + var r = ( rand.NextDouble() * range ) + bot; + return r; + } + + //static public string ToLog(this Vector2 v) => $"{v.X:0.00}, {v.Y:0.00}"; + + extension( Vector2 v ) + { + public string Log => $"{v.X:0.00}, {v.Y:0.00}"; + } + + extension( Vector3 v ) + { + public string Log => $"{v.X:0.00}, {v.Y:0.00}, {v.Z:0.00}"; + } + + + +} diff --git a/addons/core/math/MathUtil.cs.uid b/addons/core/math/MathUtil.cs.uid new file mode 100644 index 0000000..fe6fbdd --- /dev/null +++ b/addons/core/math/MathUtil.cs.uid @@ -0,0 +1 @@ +uid://qigyamlcoeb6 diff --git a/addons/core/math/Rand.cs b/addons/core/math/Rand.cs new file mode 100644 index 0000000..48496d4 --- /dev/null +++ b/addons/core/math/Rand.cs @@ -0,0 +1,32 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2025 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +//namespace client.addons.core.math; + +// No namespace, just directly access rand + +static public class rand +{ + static ThreadLocal s_rand = new( () => new Random() ); + + static rand() + { + + } + + const int c_bitForBinary = 1 << 24; + + static public double unit() => s_rand.Value.NextDouble(); + + static public bool binary() => ( s_rand.Value.NextInt64() & c_bitForBinary ) != 0; + +} diff --git a/addons/core/math/Rand.cs.uid b/addons/core/math/Rand.cs.uid new file mode 100644 index 0000000..68a1806 --- /dev/null +++ b/addons/core/math/Rand.cs.uid @@ -0,0 +1 @@ +uid://ddipuscm6hwr2 diff --git a/addons/core/math/Real.cs b/addons/core/math/Real.cs new file mode 100644 index 0000000..81050b0 --- /dev/null +++ b/addons/core/math/Real.cs @@ -0,0 +1,38 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2025 + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Godot; + + +static public class SingleEx +{ + + static public float Lerp( this float start, float end, float amount ) => start + ( end - start ) * amount; + +} + +static public class DoubleEx +{ + + static public double Lerp( this double start, double end, double amount ) => start + ( end - start ) * amount; + +} + + +static public class Vector3Ex +{ + + extension( Node3D n ) + { + public Vector3 Forward => -n.Transform.Basis.Z; + public Vector3 Right => n.Transform.Basis.X; + } +} + diff --git a/addons/core/math/Real.cs.uid b/addons/core/math/Real.cs.uid new file mode 100644 index 0000000..76ae8b2 --- /dev/null +++ b/addons/core/math/Real.cs.uid @@ -0,0 +1 @@ +uid://vv5n0q4hbsbk diff --git a/addons/core/misc/Helpers.cs b/addons/core/misc/Helpers.cs new file mode 100644 index 0000000..771b1de --- /dev/null +++ b/addons/core/misc/Helpers.cs @@ -0,0 +1,201 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2024 + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; + +using Godot; + + +using GodotDictionary = Godot.Collections.Dictionary; + +public static class U +{ + public static string NamePath( this Node current ) + { + if( current is null ) + return ""; + + var curName = current.Name.ToString(); + + return current.GetParent().NamePath() + "/" + curName; + } + + /// + /// Creates a containing the necessary keys for use with an object overriding + /// _GetPropertyList. + /// + public static GodotDictionary MakeProperty( + Type classType, + string name, + Variant.Type type, + PropertyUsageFlags usage = PropertyUsageFlags.Default, + PropertyHint hint = PropertyHint.None, + string? hintString = "" ) + { + return new GodotDictionary + { + ["name"] = name, + ["class_name"] = classType.Name, + ["type"] = Variant.From( type ), + ["usage"] = Variant.From( usage ), + ["hint"] = Variant.From( hint ), + ["hint_string"] = hintString ?? "" + }; + } + + /// + /// Create a hint string for the given enum type to be used when setting the hint_string entry of a + /// _GetPropertyList result. + /// + /// + public static string EnumHintString() + where T : Enum + { + var s = string.Empty; + foreach( var e in Enum.GetValues( typeof( T ) ) ) + { + if( !string.IsNullOrWhiteSpace( s ) ) + s += ","; + s += $"{e}:{(int)e}"; + } + return s; + } + + public static string EnumHintString( Type type ) + { + var s = string.Empty; + foreach( var e in Enum.GetValues( type ) ) + { + if( !string.IsNullOrWhiteSpace( s ) ) + s += ","; + s += $"{e}:{(int)e}"; + } + return s; + } + + private static readonly HashSet ActionTypes = new() { + typeof(Action), typeof(Action<>), typeof(Action<,>), typeof(Action<,,>), typeof(Action<,,,>), + typeof(Action<,,,,>), typeof(Action<,,,,,>), typeof(Action<,,,,,,>) + }; + + private static readonly HashSet FunctionTypes = new() { + typeof(Func<>), typeof(Func<,>), typeof(Func<,,>), typeof(Func<,,,>), + typeof(Func<,,,,>), typeof(Func<,,,,,>), typeof(Func<,,,,,,>) + }; + + private static bool TypeIsAction( Type t ) + => ActionTypes.Contains( t ) || ( t.IsGenericType && ActionTypes.Contains( t.GetGenericTypeDefinition() ) ); + + private static bool TypeIsFunc( Type t ) + => FunctionTypes.Contains( t ) || ( t.IsGenericType && FunctionTypes.Contains( t.GetGenericTypeDefinition() ) ); + + /// + /// Attempt to retrieve a function reference to the method with the given methodName in the given + /// targetNode. + /// + /// + /// The delegate for the method, or null if the editor is currently running. This is to avoid throwing + /// unnecessary exceptions when using a Tool class in the editor. This function will not return null + /// during application runtime. + /// + /// + /// The method is not found or has invalid parameters. + /// + public static T? GetMethodDelegateForNode( Node targetNode, string methodName ) + where T : Delegate + { + if( targetNode is null ) + { + throw new ArgumentNullException( nameof( targetNode ) ); + } + + if( !TypeIsAction( typeof( T ) ) && !TypeIsFunc( typeof( T ) ) ) + { + throw new InvalidOperationException( "The delegate being created must be of type Action or Func" ); + } + + var method = targetNode.GetType() + .GetMethods( BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance ) + .FirstOrDefault( m => m.Name == methodName ); + + if( method is null ) + { + // Method with given name not found + //if (Engine.EditorHint) { + // If the editor is running, we do not any information on non-Tool types. We don't want to throw + // errors when there isn't actually a problem, so let's just let the caller deal with it. + // return null; + //} + + throw new ArgumentException( $"Unable to find method \"{methodName}\" in node \"{targetNode.Name}\"" ); + } + + if( TypeIsAction( typeof( T ) ) ) + { + var generics = typeof( T ).GetGenericArguments(); + if( method.GetParameters().Count() != generics.Count() + || method.GetParameters() + .Zip( generics, ( p, t ) => (p, t) ) + .Any( pair => pair.p.ParameterType != pair.t ) ) + { + // Arguments don't match + throw new ArgumentException( $"Invalid parameters for method \"{methodName}\"" ); + } + } + else if( TypeIsFunc( typeof( T ) ) ) + { + var generics = typeof( T ).GetGenericArguments(); + if( generics.Any() ) + { + var ret = generics[0]; + var args = generics.Skip( 1 ).ToList(); + + if( method.ReturnType != ret ) + { + throw new ArgumentException( $"Invalid return type for method \"{methodName}\"" ); + } + + if( method.GetParameters() + .Zip( args, ( p, t ) => (p, t) ) + .Any( pair => pair.p.ParameterType != pair.t ) ) + { + throw new ArgumentException( $"Invalid parameter types for method \"{methodName}\"" ); + } + } + } + + return (T)Delegate.CreateDelegate( typeof( T ), targetNode, methodName ); + } +} + +/// +/// Dummy attribute to mark exports that are already handled by an overridden _GetPropertyList. +/// +[AttributeUsage( AttributeTargets.Field | AttributeTargets.Property )] +public class ExportFakeAttribute : Attribute +{ + public ExportFakeAttribute() + { } +} + +public static class NodeExtension +{ + public static IEnumerable GetChildNodes( this Node node ) + => node.GetChildren().Cast(); +} + +// Needed to prevent some dumb ass error. +namespace System.Runtime.CompilerServices +{ + internal static class IsExternalInit { } +} diff --git a/addons/core/misc/Helpers.cs.uid b/addons/core/misc/Helpers.cs.uid new file mode 100644 index 0000000..eece8bc --- /dev/null +++ b/addons/core/misc/Helpers.cs.uid @@ -0,0 +1 @@ +uid://c26dkyji1wixy diff --git a/addons/core/misc/OLib.cs b/addons/core/misc/OLib.cs new file mode 100644 index 0000000..4543155 --- /dev/null +++ b/addons/core/misc/OLib.cs @@ -0,0 +1,25 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2024 +using Godot; +using System; + +[GlobalClass] +public partial class OLib : Node +{ + // Called when the node enters the scene tree for the first time. + public override void _Ready() + { + } + + // Called every frame. 'delta' is the elapsed time since the previous frame. + public override void _Process( double delta ) + { + } + + + public void Info( string msg ) => log.info( msg ); + +} diff --git a/addons/core/misc/OLib.cs.uid b/addons/core/misc/OLib.cs.uid new file mode 100644 index 0000000..aa3453d --- /dev/null +++ b/addons/core/misc/OLib.cs.uid @@ -0,0 +1 @@ +uid://cajpiw207t04y diff --git a/addons/core/misc/POD.cs b/addons/core/misc/POD.cs new file mode 100644 index 0000000..37b8a09 --- /dev/null +++ b/addons/core/misc/POD.cs @@ -0,0 +1,300 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2024 + +#nullable enable + +using Godot; +//using Microsoft.CodeAnalysis.CSharp.Syntax; + +using Godot.Collections; +using System; +using System.Collections.Generic; +using sr = System.Reflection; +using gb = Godot.Bridge; +using System.Collections.Immutable; +using System.Reflection; +using System.Collections; + +public readonly struct PropertyInfo_Test +{ + public Variant.Type Type { get; init; } + + public StringName Name { get; init; } + + public PropertyHint Hint { get; init; } + + public string HintString { get; init; } + + public PropertyUsageFlags Usage { get; init; } + + public StringName? ClassName { get; init; } + + public bool Exported { get; init; } +} + + +//[GlobalClass] +public partial class POD : GodotObject + where T : io.Obj, new() +{ + + static public gb.PropertyInfo GetPropertyInfo( Type type, string name ) + { + var gt = Util.CSTypeCodeToGodotVariantType( Type.GetTypeCode( type ) ); + + var propHint = type.IsEnum ? PropertyHint.Enum : PropertyHint.None; + var propString = type.IsEnum ? U.EnumHintString( type ) : ""; + + gb.PropertyInfo info = new( + gt, + name, + propHint, + propString, + PropertyUsageFlags.Default, + true + ); + + return info; + } + +#if DOCUMENTATION + public MethodInfo( + StringName name, + PropertyInfo returnVal, + MethodFlags flags, + List? arguments, + List? defaultArguments) +#endif + + /* + static public List GetGodotMethodList() + { + return PODEnergyCom.GetGodotMethodList(); + } + */ + + //* + + static public List GetGodotMethodList_bad() + { + List methods = new(); + + var podType = typeof( POD ); + + var methodsArr = podType.GetMethods( sr.BindingFlags.DeclaredOnly | sr.BindingFlags.Public | sr.BindingFlags.NonPublic ); + + foreach( var mi in methodsArr ) + { + var retInfo = GetPropertyInfo( mi.ReturnType, "return" ); + + List argsInfo = new(); + + foreach( var pi in mi.GetParameters() ) + { + //sr.ParameterInfo; + GetPropertyInfo( pi.ParameterType, pi.Name! ); + } + + gb.MethodInfo gmi = new() + { + Name = mi.Name, + Flags = MethodFlags.Default, + ReturnVal = retInfo, + Arguments = argsInfo + + }; + + + methods.Add( gmi ); + } + + //methods.AddRange( methodsArr ); + + return methods; + } + // */ + + /* + public gb.PropertyInfo ReturnVal { get; init; } + + public MethodFlags Flags { get; init; } + + public int Id { get; init; } + + public List? Arguments { get; init; } + + public List? DefaultArguments { get; init; } + */ + + + private T _pod; + + public T Pod + { + get => _pod; + + set + { + _pod = value; + } + } + +#nullable disable + public POD() + { + Pod = new T(); + } +#nullable enable + + public POD( T in_pod ) + { + _pod = in_pod; + } + + + + + public Type GetPODType() + { + return typeof( T ); + } + + ImmutableDictionary _properties = ImmutableDictionary.Empty; + + //* + public override Variant _Get( StringName property ) + { + var dbgName = property.ToString(); + var prop = base._Get( property ); + + log.info( cat: "pod", msg: $"{GetType().Name}._Get: {dbgName} = {prop}" ); + + if( property == "script" && prop.VariantType == Variant.Type.Nil ) + { + prop = Variant.CreateFrom( new StringName( $"{log.thisFilePath()}" ) ); + } + else + { + //Skip this + } + + if( _properties.TryGetValue( property, out var fi ) ) + { + var val = fi.GetValue( _pod ); + var variant = Util.Convert( val?.GetType(), val ); + + return variant; + } + + return prop; + } + //*/ + + public override void _Notification( int what ) => + base._Notification( what ); + + //* + public override Array _GetPropertyList() + { + var other = base._GetPropertyList(); + + if( other == null ) + { + other = new(); + } + + log.info( cat: "pod", msg: $"{GetType().Name} _GetPropertyList" ); + + var type = GetPODType(); + + var fields = refl.GetAllFields( type ); + foreach( var fi in fields ) + { + var tc = Type.GetTypeCode( fi.FieldType ); + var gt = Util.CSTypeCodeToGodotVariantType( tc ); + + var isEnumerable = fi.FieldType.IsAssignableTo( typeof( IEnumerable ) ); + var genericCount = fi.FieldType.GenericTypeArguments.Length; + + if( isEnumerable && genericCount != 1 ) + continue; + + + var sn = new StringName( $"{refl.PrettyName( fi )}" ); + + var usage = PropertyUsageFlags.Default; + var hint = !fi.FieldType.IsEnum ? PropertyHint.None : PropertyHint.Enum; + var hintStr = !fi.FieldType.IsEnum ? "" : U.EnumHintString( fi.FieldType ); + + + if( typeof( T ).Name == "Com" ) + { + if( fi.FieldType.Name == "ComType" ) + usage |= PropertyUsageFlags.ReadOnly; + } + + if( isEnumerable ) + { + hint = PropertyHint.ArrayType; + hintStr = "Object"; + gt = Util.ConvertMarshalTypeToVariantType( typeof( IEnumerable ) ); + } + + if( gt == Variant.Type.Nil ) + continue; + + //usage |= PropertyUsageFlags.ReadOnly; + + var dict = U.MakeProperty( this.GetType(), sn, gt, + hint: hint, hintString: hintStr ); + + /* + (new Godot.Collections.Dictionary() + { + { "name", sn }, + { "type", (int)gt }, + { "usage", (int)usage }, + //{ "hint", } + //{ "hint_string", $"Version {pod.Meta.Version} Reason {pod.Meta.Reason}" }, + }); + */ + + _properties = _properties.Add( sn, fi ); + + + //dict[sn] = (long)gt; + + log.info( cat: "pod", msg: $"{GetType().Name} PropList: Add {sn} type {gt} tc {tc}" ); + + + + other.Add( dict ); + } + + return other; + } + //*/ + + public override bool _PropertyCanRevert( StringName property ) => + base._PropertyCanRevert( property ); + + public override Variant _PropertyGetRevert( StringName property ) => + base._PropertyGetRevert( property ); + + public override bool _Set( StringName property, Variant value ) + { + return base._Set( property, value ); + } + + public override void _ValidateProperty( Dictionary property ) => + base._ValidateProperty( property ); + public override bool Equals( object? obj ) => + base.Equals( obj ); + public override int GetHashCode() => + base.GetHashCode(); + public override string ToString() => + base.ToString(); +} diff --git a/addons/core/misc/POD.cs.uid b/addons/core/misc/POD.cs.uid new file mode 100644 index 0000000..e32dc02 --- /dev/null +++ b/addons/core/misc/POD.cs.uid @@ -0,0 +1 @@ +uid://bln74qohkdgbb diff --git a/addons/core/misc/PODNode.cs b/addons/core/misc/PODNode.cs new file mode 100644 index 0000000..f805a6b --- /dev/null +++ b/addons/core/misc/PODNode.cs @@ -0,0 +1,144 @@ +using Godot; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +[Tool] +public partial class PODNode : Node where T : class, new() +{ + private T _data = new T(); + + // We can't export a generic type 'T', but we can still access it. + public T Data + { + get => _data; + set + { + _data = value ?? new T(); + // If the data object is replaced, tell the editor to refresh the inspector. + if( Engine.IsEditorHint() ) + { + NotifyPropertyListChanged(); + } + } + } + + public override Godot.Collections.Array _GetPropertyList() + { + var properties = new Godot.Collections.Array(); + + //if (Data == null) return properties; + + // Use reflection to find all writable properties in our POD. + PropertyInfo[] podProperties = typeof( T ).GetProperties( BindingFlags.Public | BindingFlags.Instance ) + .Where( p => p.CanRead && p.CanWrite ).ToArray(); + + foreach( var prop in podProperties ) + { + // Try to map the C# type to a Godot Variant type. + var (variantType, hint, hintString) = CSharpTypeToVariant( prop.PropertyType ); + if( variantType == Variant.Type.Nil ) + continue; // Skip unsupported types. + + // Create the dictionary that Godot's Inspector understands. + properties.Add( new Godot.Collections.Dictionary + { + { "name", $"{prop.Name}" }, // Use a category for organization. + { "type", (int)variantType }, + { "usage", (int)PropertyUsageFlags.Default }, + { "hint", (int)hint }, + { "hint_string", hintString } + } ); + } + return properties; + } + + public override Variant _Get( StringName property ) + { + string propName = property.ToString(); + if( !propName.StartsWith( "Data/" ) ) + return base._Get( property ); + + string podPropName = propName.Remove( 0, 5 ); // Remove "Data/" prefix. + PropertyInfo propInfo = typeof( T ).GetProperty( podPropName ); + + if( propInfo != null ) + { + try + { + // Ensure the property can be read. + if( !propInfo.CanRead ) + { + GD.PrintErr( $"Property '{podPropName}' is not readable." ); + return Variant.From( null ); + } + + var value = Variant.From( propInfo.GetValue( Data ) ); + return value; + } + catch( Exception ex ) + { + GD.PrintErr( $"Failed to access property '{podPropName}': {ex.Message}" ); + return Variant.From( null ); + } + } + return base._Get( property ); + } + + public override bool _Set( StringName property, Variant value ) + { + string propName = property.ToString(); + //if (!propName.StartsWith("Data/")) return base._Set(property, value); + + string podPropName = propName.Remove( 0, 5 ); // Remove "Data/" prefix. + PropertyInfo propInfo = typeof( T ).GetProperty( podPropName ); + + if( propInfo != null ) + { + try + { + // Convert Godot's Variant back to the correct C# type. + object convertedValue = value.AsType( propInfo.PropertyType ); + + propInfo.SetValue( Data, convertedValue ); + return true; + } + catch( Exception ex ) + { + GD.PrintErr( $"Failed to set property '{podPropName}': {ex.Message}" ); + return false; + } + } + return base._Set( property, value ); + } + + // Helper to map C# types to Godot's Variant types and property hints. + private (Variant.Type, PropertyHint, string) CSharpTypeToVariant( Type type ) + { + if( type == typeof( int ) || type == typeof( long ) ) + return (Variant.Type.Int, PropertyHint.None, ""); + if( type == typeof( uint ) || type == typeof( ulong ) ) + return (Variant.Type.Int, PropertyHint.None, ""); + if( type == typeof( float ) || type == typeof( double ) ) + return (Variant.Type.Float, PropertyHint.None, ""); + if( type == typeof( string ) ) + return (Variant.Type.String, PropertyHint.None, ""); + if( type == typeof( bool ) ) + return (Variant.Type.Bool, PropertyHint.None, ""); + if( type == typeof( Vector2 ) ) + return (Variant.Type.Vector2, PropertyHint.None, ""); + if( type == typeof( Vector3 ) ) + return (Variant.Type.Vector3, PropertyHint.None, ""); + if( type == typeof( Color ) ) + return (Variant.Type.Color, PropertyHint.None, ""); + if( type.IsEnum ) + { + // Create a comma-separated string of enum names for a dropdown list. + string hintString = string.Join( ",", Enum.GetNames( type ) ); + return (Variant.Type.Int, PropertyHint.Enum, hintString); + } + // Add more type mappings as needed. + return (Variant.Type.Nil, PropertyHint.None, ""); // Unsupported type. + } +} diff --git a/addons/core/misc/PODNode.cs.uid b/addons/core/misc/PODNode.cs.uid new file mode 100644 index 0000000..905effc --- /dev/null +++ b/addons/core/misc/PODNode.cs.uid @@ -0,0 +1 @@ +uid://ddc51ql6c3qen diff --git a/addons/core/misc/Samples.cs b/addons/core/misc/Samples.cs new file mode 100644 index 0000000..4ee2b5b --- /dev/null +++ b/addons/core/misc/Samples.cs @@ -0,0 +1,77 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2024 +//using Microsoft.CodeAnalysis.CSharp.Syntax; + + + +// Skip for now +#if false + +using cs; +using System; +using System.IO; +using System.Security.Cryptography.X509Certificates; + +static public class Samples +{ + + public static void CreateDataSamples() + { + ser.XmlCfg cfg = new() + { + POD = ser.POD.Elements, + }; + + //cfg.POD = lib.POD.Elements; + + WriteSampleData( cfg ); + + WriteSampleData( cfg ); + + //res.Ref deckRef = new( "samples/DeckData.xml" ); + + //var deck = deckRef.res; + + WriteSampleData( cfg, () => new BattleData( DebugSample.Create ) ); + + //WriteSampleData( cfg ); + + + } + + public static void WriteSampleData( ser.XmlCfg cfg, Func fn ) where TD: cs.Data + => WriteSampleData( cfg, typeof(TD).Name, fn() ); + + + public static void WriteSampleData( ser.XmlCfg cfg ) + where TD: cs.Data, new() + => WriteSampleData( cfg, $"samples/{typeof(TD).Name}.xml", new TD() ); + + + + public static void WriteSampleData( ser.XmlCfg cfg, string path, TD entityData ) + where TD: cs.Data + { + log.high( $"WriteSampleData: Writing {typeof(TD).Name} to {path}" ); + try + { + ser.XmlSer xml = new( cfg ); + using FileStream stream = new( path, FileMode.Create ); + + xml.Serialize( stream, entityData ); + } + catch( Exception ex ) + { + log.warn( $"WriteSampleData: ex {ex.Message}" ); + log.info( $"Stack.\n{ex.StackTrace}" ); + } + } + + +} + + +#endif diff --git a/addons/core/misc/Samples.cs.uid b/addons/core/misc/Samples.cs.uid new file mode 100644 index 0000000..3b11d67 --- /dev/null +++ b/addons/core/misc/Samples.cs.uid @@ -0,0 +1 @@ +uid://cnfixc632kn4v diff --git a/addons/core/misc/Test.cs b/addons/core/misc/Test.cs new file mode 100644 index 0000000..071ac92 --- /dev/null +++ b/addons/core/misc/Test.cs @@ -0,0 +1,62 @@ +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +// +// D E R E L I C T +// +/// // (c) 2003..2024 +/* +So, someone on the Godot.CSharp channel asked about a generic way to do covariant style (I think its covariant) code for running a different function per type + +I wrote a way to do it using static lambdas on a generic class, and realized I could do a trait style system with it. + +Going to have to investigate it a bit further to see if it would make some code easier. +*/ + +using Godot; +using System; + +namespace dbg; + +public class GenericSetText +{ + static public void SetText( T v, string text ) => GenericSetText.FnSetText( v, text ); +} + +public class GenericSetText +{ + static public Action FnSetText = ( v, s ) => GD.PrintErr( $"On {typeof( T ).Name} trying to SetText" ); + static public void SetText( T v, string text ) => GenericSetText.FnSetText( v, text ); +} + +static public class GenericTest +{ + static GenericTest() + { + GenericSetText.FnSetText = ( label, s ) => + { + var type = typeof( GenericSetText<> ).MakeGenericType( label.GetType() ); + var method = type.GetMethod( "SetText", 1, new Type[1] { label.GetType() } ); + method.Invoke( label, new object[1] { s } ); + }; + + GenericSetText