Moved core into my new style

This commit is contained in:
Marc Hernandez 2026-02-01 23:07:15 -08:00
parent bfbee0c9db
commit 3ca06726c0
92 changed files with 4662 additions and 0 deletions

1
.gitignore vendored
View File

@ -15,3 +15,4 @@ export_presets.cfg
data_*/
mono_crash.*.json
.DS_Store

285
addons/core/.gitattributes vendored Normal file
View File

@ -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

67
addons/core/.gitignore vendored Normal file
View File

@ -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

View File

@ -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<Vector3> Points { get; set; } = new();
[Export]
public gdcoll.Array<gdcoll.Array<int>> 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<int>();
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<int> 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;
}
}

View File

@ -0,0 +1 @@
uid://b500r6swmdc3w

View File

@ -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<int, float>();
var cameFrom = new Dictionary<int, int>();
var gScore = new Dictionary<int, float>();
gScore[startId] = 0;
var fScore = new Dictionary<int, float>();
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<int, int> cameFrom, int currentId, AStarGraph graph )
{
var path = new List<Vector3> { 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();
}
}

View File

@ -0,0 +1 @@
uid://c7e80my8xas6l

View File

@ -0,0 +1,175 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2025
using Godot;
/// <summary>
/// A path follower that treats the agent as a sphere. The target point is the
/// intersection of this sphere with the current path segment.
/// </summary>
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;
/// <summary>
/// Checks if the agent has reached the end of the path.
/// </summary>
public bool IsFinished => _currentIndex >= _path.Length;
/// <summary>
/// Checks if the agent has reached the end of the path.
/// </summary>
public bool IsNearlyFinished => _currentIndex >= ( _path.Length - 1 );
/// <summary>
/// Initializes a new path follower with a path and a sphere radius.
/// </summary>
/// <param name="path">The array of Vector3 points to follow.</param>
/// <param name="sphereRadius">The radius of the agent's collision/interaction sphere.</param>
public SpherePathFollower( float sphereRadius )
{
_radius = sphereRadius;
SetPath( new Vector3[0], sphereRadius );
}
/// <summary>
/// Initializes a new path follower with a path and a sphere radius.
/// </summary>
/// <param name="path">The array of Vector3 points to follow.</param>
/// <param name="sphereRadius">The radius of the agent's collision/interaction sphere.</param>
public SpherePathFollower( Vector3[] path, float sphereRadius )
{
SetPath( path, sphereRadius );
}
/// <summary>
/// Assigns a new path and radius, resetting the follower's progress.
/// </summary>
public void SetPath( Vector3[] newPath )
{
SetPath( newPath, _radius );
}
/// <summary>
/// Assigns a new path and radius, resetting the follower's progress.
/// </summary>
public void SetPath( Vector3[] newPath, float sphereRadius )
{
_path = newPath ?? new Vector3[0];
_radius = Mathf.Max( 0.01f, sphereRadius ); // Ensure radius is positive
_currentIndex = 0;
}
/// <summary>
/// Calculates the next target point on the current path segment for the agent to move towards.
/// </summary>
/// <param name="agentPosition">The current world position of the agent.</param>
/// <returns>The world-space target point on the path segment.</returns>
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;
}
/// <summary>
/// Updates the follower's progress along the path. Call this after the agent moves.
/// </summary>
/// <param name="agentPosition">The agent's new world position after moving.</param>
/// <returns>True if updated</returns>
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;
}
/// <summary>
/// Updates the follower's progress along the path. Call this after the agent moves.
/// </summary>
/// <param name="agentPosition">The agent's new world position after moving.</param>
/// <returns>True if updated</returns>
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;
}
}

View File

@ -0,0 +1 @@
uid://dlfmqjpqpduxn

219
addons/core/core.cs Normal file
View File

@ -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

1
addons/core/core.cs.uid Normal file
View File

@ -0,0 +1 @@
uid://t5s8xt1qqpoq

391
addons/core/core/Game.cs Normal file
View File

@ -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<Game>( 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 );
}
*/
}
/// //
///

View File

@ -0,0 +1 @@
uid://c4g565oftdnax

View File

@ -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"]

View File

@ -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 )
{
}
}

View File

@ -0,0 +1 @@
uid://dykdxaolk0qpa

View File

@ -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<PackedScene>( 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 )
{
}
}

View File

@ -0,0 +1 @@
uid://d3uvyevm1600t

View File

@ -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<StateNode>( _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;
}
}
}

View File

@ -0,0 +1 @@
uid://cwq218bv6mw6x

View File

@ -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 );
}
}

View File

@ -0,0 +1 @@
uid://bmi8t3kreo03t

View File

@ -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 );
}
}

View File

@ -0,0 +1 @@
uid://c1cqu7yvvsd75

152
addons/core/math/Geo.cs Normal file
View File

@ -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
};
/// <summary>
/// Calculates the intersection of a sphere with a line segment, always returning a point on the segment.
/// </summary>
/// <param name="sphereCenter">The world position of the sphere's center.</param>
/// <param name="sphereRadius">The radius of the sphere.</param>
/// <param name="segmentP1">The starting point of the line segment.</param>
/// <param name="segmentP2">The ending point of the line segment.</param>
/// <param name="intersectionPoint">The resulting intersection point, clamped to the segment.</param>
/// <param name="intersectionType">The type of intersection that occurred.</param>
/// <returns>True if an intersection with the infinite line is mathematically possible, false otherwise.</returns>
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;
}
/// <summary>
/// Calculates the intersection of a sphere and a line segment.
/// </summary>
/// <param name="sphereCenter">The world position of the sphere's center.</param>
/// <param name="sphereRadius">The radius of the sphere.</param>
/// <param name="segmentP1">The starting point of the line segment.</param>
/// <param name="segmentP2">The ending point of the line segment.</param>
/// <param name="intersectionPoint">The point where the sphere first intersects the infinite line.</param>
/// <param name="intersectionType">Indicates where the intersection lies: -1 for before the segment, 0 for on the segment, 1 for after the segment.</param>
/// <returns>True if an intersection with the infinite line exists, false otherwise.</returns>
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;
}
}

View File

@ -0,0 +1 @@
uid://bsqypnus3htcr

View File

@ -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}";
}
}

View File

@ -0,0 +1 @@
uid://qigyamlcoeb6

32
addons/core/math/Rand.cs Normal file
View File

@ -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<Random> 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;
}

View File

@ -0,0 +1 @@
uid://ddipuscm6hwr2

38
addons/core/math/Real.cs Normal file
View File

@ -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;
}
}

View File

@ -0,0 +1 @@
uid://vv5n0q4hbsbk

201
addons/core/misc/Helpers.cs Normal file
View File

@ -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;
}
/// <summary>
/// Creates a <see cref="GodotDictionary"/> containing the necessary keys for use with an object overriding
/// <c>_GetPropertyList</c>.
/// </summary>
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 ?? ""
};
}
/// <summary>
/// Create a hint string for the given enum type to be used when setting the <c>hint_string</c> entry of a
/// <c>_GetPropertyList</c> result.
/// </summary>
/// <seealso cref="MakeProperty"/>
public static string EnumHintString<T>()
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<Type> ActionTypes = new() {
typeof(Action), typeof(Action<>), typeof(Action<,>), typeof(Action<,,>), typeof(Action<,,,>),
typeof(Action<,,,,>), typeof(Action<,,,,,>), typeof(Action<,,,,,,>)
};
private static readonly HashSet<Type> 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() ) );
/// <summary>
/// Attempt to retrieve a function reference to the method with the given <c>methodName</c> in the given
/// <c>targetNode</c>.
/// </summary>
/// <returns>
/// The delegate for the method, or <c>null</c> if the editor is currently running. This is to avoid throwing
/// unnecessary exceptions when using a <c>Tool</c> class in the editor. This function will not return <c>null</c>
/// during application runtime.
/// </returns>
/// <exception cref="ArgumentException">
/// The method is not found or has invalid parameters.
/// </exception>
public static T? GetMethodDelegateForNode<T>( 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 );
}
}
/// <summary>
/// Dummy attribute to mark exports that are already handled by an overridden <c>_GetPropertyList</c>.
/// </summary>
[AttributeUsage( AttributeTargets.Field | AttributeTargets.Property )]
public class ExportFakeAttribute : Attribute
{
public ExportFakeAttribute()
{ }
}
public static class NodeExtension
{
public static IEnumerable<Node> GetChildNodes( this Node node )
=> node.GetChildren().Cast<Node>();
}
// Needed to prevent some dumb ass error.
namespace System.Runtime.CompilerServices
{
internal static class IsExternalInit { }
}

View File

@ -0,0 +1 @@
uid://c26dkyji1wixy

25
addons/core/misc/OLib.cs Normal file
View File

@ -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 );
}

View File

@ -0,0 +1 @@
uid://cajpiw207t04y

300
addons/core/misc/POD.cs Normal file
View File

@ -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<T> : 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<PropertyInfo>? arguments,
List<Variant>? defaultArguments)
#endif
/*
static public List<gb.MethodInfo> GetGodotMethodList()
{
return PODEnergyCom.GetGodotMethodList();
}
*/
//*
static public List<gb.MethodInfo> GetGodotMethodList_bad()
{
List<gb.MethodInfo> methods = new();
var podType = typeof( POD<T> );
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<gb.PropertyInfo> 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<gb.PropertyInfo>? Arguments { get; init; }
public List<Variant>? 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<StringName, FieldInfo> _properties = ImmutableDictionary<StringName, FieldInfo>.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<Dictionary> _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();
}

View File

@ -0,0 +1 @@
uid://bln74qohkdgbb

144
addons/core/misc/PODNode.cs Normal file
View File

@ -0,0 +1,144 @@
using Godot;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
[Tool]
public partial class PODNode<T> : 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<Godot.Collections.Dictionary> _GetPropertyList()
{
var properties = new Godot.Collections.Array<Godot.Collections.Dictionary>();
//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<GodotObject>( 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<GodotObject>( 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.
}
}

View File

@ -0,0 +1 @@
uid://ddc51ql6c3qen

View File

@ -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<EntityData>( cfg );
WriteSampleData<CardData>( cfg );
//res.Ref<DeckData> deckRef = new( "samples/DeckData.xml" );
//var deck = deckRef.res;
WriteSampleData( cfg, () => new BattleData( DebugSample.Create ) );
//WriteSampleData<DeckData>( cfg );
}
public static void WriteSampleData<TD>( ser.XmlCfg cfg, Func<TD> fn ) where TD: cs.Data
=> WriteSampleData( cfg, typeof(TD).Name, fn() );
public static void WriteSampleData<TD>( ser.XmlCfg cfg )
where TD: cs.Data, new()
=> WriteSampleData( cfg, $"samples/{typeof(TD).Name}.xml", new TD() );
public static void WriteSampleData<TD>( 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

View File

@ -0,0 +1 @@
uid://cnfixc632kn4v

62
addons/core/misc/Test.cs Normal file
View File

@ -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>( T v, string text ) => GenericSetText<T>.FnSetText( v, text );
}
public class GenericSetText<T>
{
static public Action<T, string> FnSetText = ( v, s ) => GD.PrintErr( $"On {typeof( T ).Name} trying to SetText" );
static public void SetText( T v, string text ) => GenericSetText<T>.FnSetText( v, text );
}
static public class GenericTest
{
static GenericTest()
{
GenericSetText<Control>.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<Label>.FnSetText = ( label, s ) => label.Text = s;
GenericSetText<Button>.FnSetText = ( button, s ) => button.Text = s;
GenericSetText<TextEdit>.FnSetText = ( textEdit, s ) => textEdit.Text = s;
GenericSetText<RichTextLabel>.FnSetText = ( richText, s ) => richText.Text = s;
GenericSetText<string>.FnSetText = ( v, s ) => { };
}
static public void RunTests()
{
Control controlTest = new Button();
GenericSetText.SetText( controlTest, "Button text" );
Button buttonTest = new();
GenericSetText.SetText( buttonTest, "Button text" );
Label labelTest = new();
GenericSetText.SetText( labelTest, "Label Text" );
TextEdit textEditTest = new();
GenericSetText.SetText( textEditTest, "Label Text" );
int integerTest = 10;
GenericSetText.SetText( integerTest, "Integer Test" );
}
}

View File

@ -0,0 +1 @@
uid://clm854vcbb75s

550
addons/core/misc/Util.cs Normal file
View File

@ -0,0 +1,550 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
//using Microsoft.CodeAnalysis.CSharp.Syntax;
using Godot.Collections;
using gen = System.Collections.Generic;
using Godot.Sharp.Extras;
using System;
using IO = System.IO;
using System.Diagnostics;
using Godot.NativeInterop;
using System.Reflection;
using gb = Godot.Bridge;
using System.Runtime.InteropServices.ObjectiveC;
using System.Runtime.CompilerServices;
using System.Xml;
using System.Buffers;
using System.Collections.Immutable;
namespace time
{
public record struct AbsUSec
{
static public AbsUSec Now => new AbsUSec( Time.GetTicksUsec() );
ulong _usec;
public ulong Abs => _usec;
public double Seconds => (double)_usec / 1_000_000.0;
#region Cons
public AbsUSec( ulong usec )
{
_usec = usec;
}
public AbsUSec( long usec )
{
Debug.Assert( usec > 0 );
_usec = (ulong)usec;
}
#endregion
static public AbsUSec operator +( AbsUSec left, RelUSec right ) => new AbsUSec( (long)left.Abs + right.Rel );
static public RelUSec operator -( AbsUSec left, AbsUSec right ) => new RelUSec( (long)left.Abs - (long)right.Abs );
}
public record struct RelUSec
{
long _usec;
public long Rel => _usec;
public double Seconds => (double)_usec / 1_000_000.0;
public RelUSec( long usec )
{
_usec = usec;
}
static public RelUSec operator -( RelUSec left, RelUSec right ) => new RelUSec( (long)left.Rel - (long)right.Rel );
}
}
public record struct TargetInfo
{
public readonly time.AbsUSec LastSeen = time.AbsUSec.Now;
public readonly CollisionInfo3 Info;
public float HowOldSec => (float)( ( time.AbsUSec.Now - LastSeen ).Rel ) / 1_000_000.0f;
public TargetInfo( CollisionInfo3 info )
{
Info = info;
}
}
static public class Util
{
static public ImmutableList<T> FindAll<T>( this Node root ) where T : Node
{
log.debug( $"FindAll<{typeof( T )}>: {root.GetPath()}" );
var children = root.GetNodesOfType<T>();
foreach( var ch in children )
{
if( ch == null )
{
log.warn( $"Found null in list" );
}
}
var immList = children.ToImmutableList();
var allChildren = root.GetChildren();
foreach( var child in allChildren )
{
var childList = child.FindAll<T>();
immList = immList.AddRange( childList );
}
return immList;
}
static public bool VerifyNotNull( this Node node, bool logInfo = true,
[CallerMemberName] string dbgName = "",
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerArgumentExpression("node")]
string dbgExp_node = ""
)
{
var nodeIsNotNull = node != null;
var loc = new SourceLoc( "", dbgName, dbgPath, dbgLine );
if( logInfo && !nodeIsNotNull )
{
log.warn( $"{loc.Log}: {dbgExp_node} IS NULL" );
}
else
{
log.info( $"{loc.Log}: {dbgExp_node} is not null" );
}
return nodeIsNotNull;
}
#region Math
static public string Debug( this Vector3 vec )
=> $"({vec.X:0.00}, {vec.X:0.00}, {vec.X:0.00})";
static public string DebugX( this Vector3 vec )
=> $"({vec.X:0.00}, {vec.X:0.00})";
#endregion
#region Strings
static public string SafeOr( this string str, string def ) =>
!string.IsNullOrWhiteSpace( str ) ? str : def;
static public bool NOW( this string str ) => string.IsNullOrWhiteSpace( str );
static public bool TryGetUserArgValue( this string[] strings, string whatArg, out string value )
{
var index = 0;
foreach( var arg in strings )
{
if( arg == whatArg && index + 1 < strings.Length )
{
value = strings[index + 1];
return true;
}
++index;
}
value = "";
return false;
}
static public bool UserArgExists( this string[] strings, string whatArg ) => strings.Contains( whatArg );
static public string RelTime( this DateTime dt )
{
var span = DateTime.Now - dt;
if( span.TotalMilliseconds < 0 )
return $"Never";
if( span.TotalMilliseconds < 1000 )
return $"{(int)span.TotalMilliseconds}ms";
if( span.TotalMilliseconds < 100 * 1000 )
return $"{(int)( span.TotalMilliseconds / 1000.0 )}sec";
//If Under a year
if( span.TotalMilliseconds < ( 52.0 * 168.0 * 60.0 * 60.0 * 1000.0 ) )
return $"{(int)( span.TotalMilliseconds / 60 * 1000.0 )}min";
return $"Always";
}
#endregion
#region Res
static string ResProxySer( object obj )
{
if( obj is Resource res )
{
log.info( $"Res path from {res.ResourcePath}" );
return res.ResourcePath;
}
return "";
}
static object ResProxyDes( string type, string str )
{
log.info( $"Loading res {type} from {str}" );
var res = ResourceLoader.Load( str, type );
return res;
}
static ser.TypeProxy ResourceProxy = new( ResProxySer, ResProxyDes );
static gen.Dictionary<Type, ser.TypeProxy> TypeProxy = new() {
{ typeof(Resource), ResourceProxy },
};
static ser.XmlCfg XmlCfg = new()
{
Verbose = true,
//Were not saving abitrary graphs
Structure = ser.Datastructure.Tree,
//FOr Godot types we cant otherwise save
Proxies = TypeProxy.ToImmutableDictionary(),
//For loading and saving Godot subclasses, we default to doing nothing
TypesDefault = ser.Types.None,
};
#endregion
#region SaveLoad
static string s_saveDir = "save";
static string s_saveExt = "xml";
static public void SetupSaveState( string saveDir = "save", string saveExt = "xml" )
{
log.info( $"Setting up save state in {saveDir} with ext {saveExt}" );
s_saveDir = saveDir;
s_saveExt = saveExt;
lib.Util.checkAndAddDirectory( $"{s_saveDir}" );
}
static public void SaveState( this Control control )
{
var controlName = control.Name;
var filename = $"{s_saveDir}/{controlName}.{s_saveExt}";
try
{
ser.XmlSer xml = new( XmlCfg );
if( IO.File.Exists( filename ) )
{
// @@ TODO Backup the existing saves
}
using var stream = new IO.FileStream( filename, IO.FileMode.Create );
xml.Serialize( stream, control );
}
catch( Exception ex )
{
log.error( $"Ex {ex.Message} trying to save control {control.Name} into {filename}" );
}
}
static public void LoadState<T>( this T control, bool verbose = false )
where T : Control
{
var controlName = control.Name;
var filename = $"{s_saveDir}/{controlName}.{s_saveExt}";
try
{
ser.XmlSer xml = new( XmlCfg );
if( !IO.File.Exists( filename ) )
{
return;
}
using var stream = new IO.FileStream( filename, IO.FileMode.Open );
xml.DeserializeInto<T>( stream, control );
}
catch( Exception ex )
{
log.error( $"Ex {ex.Message} trying to save control {control.Name} into {filename}" );
}
}
#endregion
static public Godot.Variant.Type CSTypeCodeToGodotVariantType( TypeCode tc )
{
switch( tc )
{
case TypeCode.Empty:
return Variant.Type.Nil;
case TypeCode.Boolean:
return Variant.Type.Bool;
case TypeCode.DateTime:
return Variant.Type.Nil;
case TypeCode.DBNull:
return Variant.Type.Nil;
case TypeCode.Decimal:
case TypeCode.Single:
case TypeCode.Double:
return Variant.Type.Float;
case TypeCode.Char:
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
return Variant.Type.Int;
case TypeCode.Byte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
return Variant.Type.Int;
case TypeCode.Object:
return Variant.Type.Object;
case TypeCode.String:
return Variant.Type.String;
}
return Variant.Type.Nil;
}
static public Variant.Type ConvertMarshalTypeToVariantType( Type type )
{
if( type == typeof( Boolean ) )
return Variant.Type.Bool;
if( type == typeof( Char ) )
return Variant.Type.Int;
if( type == typeof( SByte ) )
return Variant.Type.Int;
if( type == typeof( Int16 ) )
return Variant.Type.Int;
if( type == typeof( Int32 ) )
return Variant.Type.Int;
if( type == typeof( Int64 ) )
return Variant.Type.Int;
if( type == typeof( Byte ) )
return Variant.Type.Int;
if( type == typeof( UInt16 ) )
return Variant.Type.Int;
if( type == typeof( UInt32 ) )
return Variant.Type.Int;
if( type == typeof( UInt64 ) )
return Variant.Type.Int;
if( type == typeof( Single ) )
return Variant.Type.Float;
if( type == typeof( Double ) )
return Variant.Type.Float;
if( type == typeof( String ) )
return Variant.Type.String;
if( type == typeof( Vector2 ) )
return Variant.Type.Vector2;
if( type == typeof( Vector2I ) )
return Variant.Type.Vector2I;
if( type == typeof( Rect2 ) )
return Variant.Type.Rect2;
if( type == typeof( Rect2I ) )
return Variant.Type.Rect2I;
if( type == typeof( Transform2D ) )
return Variant.Type.Transform2D;
if( type == typeof( Vector3 ) )
return Variant.Type.Vector3;
if( type == typeof( Vector3I ) )
return Variant.Type.Vector3I;
if( type == typeof( Basis ) )
return Variant.Type.Basis;
if( type == typeof( Quaternion ) )
return Variant.Type.Quaternion;
if( type == typeof( Transform3D ) )
return Variant.Type.Transform3D;
if( type == typeof( Vector4 ) )
return Variant.Type.Vector4;
if( type == typeof( Vector4I ) )
return Variant.Type.Vector4I;
if( type == typeof( Projection ) )
return Variant.Type.Projection;
if( type == typeof( Aabb ) )
return Variant.Type.Aabb;
if( type == typeof( Color ) )
return Variant.Type.Color;
if( type == typeof( Plane ) )
return Variant.Type.Plane;
if( type == typeof( Callable ) )
return Variant.Type.Callable;
if( type == typeof( Signal ) )
return Variant.Type.Signal;
if( type == typeof( Enum ) )
return Variant.Type.Int;
if( type == typeof( StringName ) )
return Variant.Type.StringName;
if( type == typeof( NodePath ) )
return Variant.Type.NodePath;
if( type == typeof( Rid ) )
return Variant.Type.Rid;
if( type == typeof( Variant ) )
return Variant.Type.Nil;
if( type == typeof( System.Collections.IEnumerable ) )
return Variant.Type.Array;
/*
if( type == typeof( GodotObjectOrDerivedArray ) ) return Variant.Type.Array;
if( type == typeof( ByteArray ) ) return Variant.Type.PackedByteArray;
if( type == typeof( Int32Array ) ) return Variant.Type.PackedInt32Array;
if( type == typeof( Int64Array ) ) return Variant.Type.PackedInt64Array;
if( type == typeof( Float32Array ) ) return Variant.Type.PackedFloat32Array;
if( type == typeof( Float64Array ) ) return Variant.Type.PackedFloat64Array;
if( type == typeof( StringArray ) ) return Variant.Type.PackedStringArray;
if( type == typeof( Vector2Array ) ) return Variant.Type.PackedVector2Array;
if( type == typeof( Vector3Array ) ) return Variant.Type.PackedVector3Array;
if( type == typeof( ColorArray ) ) return Variant.Type.PackedColorArray;
if( type == typeof( SystemArrayOfStringName ) ) return Variant.Type.Array;
if( type == typeof( SystemArrayOfNodePath ) ) return Variant.Type.Array;
if( type == typeof( SystemArrayOfRid ) ) return Variant.Type.Array;
if( type == typeof( GodotObjectOrDerived ) ) return Variant.Type.Object;
if( type == typeof( GodotDictionary ) ) return Variant.Type.Dictionary;
if( type == typeof( GodotGenericDictionary ) ) return Variant.Type.Dictionary;
if( type == typeof( GodotGenericArray ) ) return Variant.Type.Array;
*/
return Variant.Type.Nil;
}
static public Godot.Variant Convert( Type type, object obj )
{
var tc = Type.GetTypeCode( type );
switch( tc )
{
case TypeCode.Empty:
return new Variant();
case TypeCode.Boolean:
return Variant.From( (bool)obj );
case TypeCode.DateTime:
return new Variant();
case TypeCode.DBNull:
return new Variant();
case TypeCode.Decimal:
case TypeCode.Single:
case TypeCode.Double:
return Variant.From( (double)obj );
case TypeCode.Char:
case TypeCode.SByte:
case TypeCode.Int16:
case TypeCode.Int32:
case TypeCode.Int64:
return Variant.From( (int)obj );
case TypeCode.Byte:
case TypeCode.UInt16:
case TypeCode.UInt32:
case TypeCode.UInt64:
return Variant.From( (int)obj );
case TypeCode.String:
return Variant.From( (string)obj );
/*
case TypeCode.Object:
{
// @@@ TODO Return proxy object
//return Variant.From( obj );
//return new Variant();
if( obj is EntityId entId )
{
return Variant.From( entId.id );
}
}
break;
*/
}
return new Variant();
}
static public Array<T> ToGDArray<[MustBeVariant] T>( this gen.IEnumerable<T> coll )
{
var arr = new Array<T>();
foreach( var t in coll )
{
//GD.Print( $"Processing {coll.GetType()}" );
if( t == null )
{
GD.PrintErr( $"ToGDArray<{typeof( T ).Name}> found a null object" );
continue;
}
arr.Add( t );
}
return arr;
}
static public gen.IEnumerable<CollisionInfo3> Colliders( this ShapeCast3D sc )
{
var count = sc.GetCollisionCount();
for( int i = 0; i < count; ++i )
{
var ci = new CollisionInfo3()
{
position = sc.GetCollisionPoint( i ),
normal = sc.GetCollisionNormal( i ),
collider = sc.GetCollider( i ),
rid = sc.GetColliderRid( i ),
};
yield return ci;
}
}
}

View File

@ -0,0 +1 @@
uid://c7og7inhkhxpc

View File

@ -0,0 +1,60 @@
using Godot;
using System;
public static class VariantExtensions
{
/// <summary>
/// Converts a Godot Variant to a specified C# object type using a switch statement.
/// </summary>
/// <param name="variant">The variant to convert.</param>
/// <param name="targetType">The C# System.Type to convert to.</param>
/// <returns>An object of the target type, or throws an exception if conversion is not supported.</returns>
public static object AsType( this Variant variant, Type targetType )
{
// The switch statement uses type pattern matching (`when t == ...`)
// to check against the desired target type.
switch( targetType )
{
// Basic C# types
case Type t when t == typeof( bool ):
return variant.As<bool>();
case Type t when t == typeof( int ):
return variant.As<int>();
case Type t when t == typeof( long ):
return variant.As<long>();
case Type t when t == typeof( float ):
return variant.As<float>();
case Type t when t == typeof( double ):
return variant.As<double>();
case Type t when t == typeof( string ):
return variant.As<string>();
// Godot-specific structs
case Type t when t == typeof( Vector2 ):
return variant.As<Vector2>();
case Type t when t == typeof( Vector3 ):
return variant.As<Vector3>();
case Type t when t == typeof( Color ):
return variant.As<Color>();
// Handle enums (which are stored as integers in Variants)
case Type t when t.IsEnum:
return Enum.ToObject( t, variant.As<long>() );
// Handle Godot Objects (Nodes, Resources, etc.)
case Type t when t.IsSubclassOf( typeof( GodotObject ) ):
return variant.AsGodotObject(); // Use the built-in As(Type) for engine types
// Default case for unsupported types
default:
throw new ArgumentException( $"Conversion from Variant to type '{targetType.Name}' is not supported by this method." );
}
}
}

View File

@ -0,0 +1 @@
uid://cw2w7f8iljtxd

256
addons/core/misc/ser.cs Normal file
View File

@ -0,0 +1,256 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
//using Microsoft.CodeAnalysis.CSharp.Syntax;
using gen = System.Collections.Generic;
using System;
using System.Text;
public static class SerExtensions
{
public static T GetRefl<T>( Godot.Collections.Dictionary dict )
where T : new()
{
T v = new T();
Type type = typeof( T );
Type utilType = typeof( SerExtensions );
var getReflMethod = utilType.GetMethod( "GetRefl" );
var parseArrayMethod = utilType.GetMethod( "ParseReflArray" );
//GD.Print($"GetRefl dict {dict}");
//GD.Print($"GetRefl type {type.Name}");
var props = type.GetProperties();
foreach( var prop in props )
{
var nameBld = new StringBuilder( prop.Name );
nameBld[0] = char.ToLower( nameBld[0] );
var name = nameBld.ToString();
var propType = prop.PropertyType;
try
{
if( propType.IsArray )
{
GD.Print( $"Skipping Array {name}" );
continue;
}
if( propType.IsEnum )
{
propType = propType.GetEnumUnderlyingType();
}
TypeCode tc = Type.GetTypeCode( propType );
//GD.Print($"{name}: {propType.Name} {tc}");
switch( tc )
{
case TypeCode.Boolean:
prop.SetValue( v, GetBool( dict, name ) );
break;
case TypeCode.Char:
prop.SetValue( v, (char)GetLong( dict, name ) );
break;
case TypeCode.DateTime:
case TypeCode.DBNull:
case TypeCode.Decimal:
case TypeCode.Empty:
{
GD.Print( $"GetRefl ERROR Unhandled type for {name}: {propType.Name}" );
}
break;
case TypeCode.Single:
prop.SetValue( v, (float)GetDouble( dict, name ) );
break;
case TypeCode.Double:
prop.SetValue( v, GetDouble( dict, name ) );
break;
case TypeCode.Int16:
prop.SetValue( v, (short)GetLong( dict, name ) );
break;
case TypeCode.Int32:
prop.SetValue( v, (int)GetLong( dict, name ) );
break;
case TypeCode.Int64:
prop.SetValue( v, GetLong( dict, name ) );
break;
case TypeCode.SByte:
prop.SetValue( v, (sbyte)GetULong( dict, name ) );
break;
case TypeCode.Byte:
prop.SetValue( v, (byte)GetLong( dict, name ) );
break;
case TypeCode.UInt16:
prop.SetValue( v, (ushort)GetULong( dict, name ) );
break;
case TypeCode.UInt32:
prop.SetValue( v, (uint)GetULong( dict, name ) );
break;
case TypeCode.UInt64:
prop.SetValue( v, GetULong( dict, name ) );
break;
case TypeCode.Object:
{
if( propType.Name == "gen.IEnumerable`1" )
{
GD.Print( $"GetRefl Type Filling in gen.IEnumerable {name}: {propType.Name} {propType.GenericTypeArguments[0].Name}" );
var enumerableType = propType.GenericTypeArguments[0];
//*
if( enumerableType.Name == "String" )
{
enumerableType = typeof( string );
}
// */
var genericParseReflArrayMethod = parseArrayMethod.MakeGenericMethod( enumerableType );
var obj = genericParseReflArrayMethod.Invoke( null, new object[] {
dict,
name,
} );
prop.SetValue( v, obj );
}
else
{
var genericGetReflMethod = getReflMethod.MakeGenericMethod( propType );
var obj = genericGetReflMethod.Invoke( null, new object[] {
GetObject(dict, name)
} );
prop.SetValue( v, obj );
}
}
break;
case TypeCode.String:
prop.SetValue( v, GetString( dict, name ) );
break;
}
}
catch( ArgumentException ex )
{
GD.Print( $"While parsing {type.Name}: {name}" );
GD.Print( $"Caught {ex.GetType().Name} {ex.Message}" );
}
catch( Exception ex )
{
GD.Print( $"While parsing {type.Name}: {name}" );
GD.Print( $"Caught {ex.GetType().Name} {ex.Message}" );
}
}
return v;
}
public static string GetString( Godot.Collections.Dictionary dict, string name )
{
var v = (string)dict[name];
return v;
}
public static long GetLong( Godot.Collections.Dictionary dict, string name )
{
var val = (float)dict[name];
var v = (long)val;
return v;
}
public static ulong GetULong( Godot.Collections.Dictionary dict, string name )
{
var val = (float)dict[name];
var v = (ulong)val;
return v;
}
public static bool GetBool( Godot.Collections.Dictionary dict, string name )
{
var v = (bool)dict[name];
return v;
}
public static double GetDouble( Godot.Collections.Dictionary dict, string name )
{
var val = (float)dict[name];
return (double)val;
}
public static Godot.Collections.Array GetArray( Godot.Collections.Dictionary dict, string name )
{
var arr = (Godot.Collections.Array)dict[name];
return arr;
}
public static Godot.Collections.Dictionary GetObject( Godot.Collections.Dictionary dict, string name )
{
var d2 = (Godot.Collections.Dictionary)dict[name];
return d2;
}
public static gen.List<T> ParseArray<T>( Godot.Collections.Dictionary dict, string name, Func<Godot.Collections.Dictionary, T> fn )
{
var arr = new gen.List<T>();
var dictArr = GetArray( dict, name );
foreach( var arrObj in dictArr )
{
arr.Add( fn( (Godot.Collections.Dictionary)arrObj ) );
}
return arr;
}
public static gen.List<T> ParseReflArray<T>( Godot.Collections.Dictionary dict, string name )
where T : new()
{
var arr = new gen.List<T>();
var dictArr = GetArray( dict, name );
foreach( var arrObj in dictArr )
{
arr.Add( GetRefl<T>( (Godot.Collections.Dictionary)arrObj ) );
}
return arr;
}
}

View File

@ -0,0 +1 @@
uid://dyar18662and7

View File

@ -0,0 +1,107 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2025
using Godot;
using Godot.Sharp.Extras;
using System;
[GlobalClass]
public partial class DirSpawner : MultiplayerSpawner
{
[Export( PropertyHint.Dir )]
public string Directory;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
base._Ready();
this.OnReady();
log.info( $"{log.loc().Log}: Searching for spawns in {Directory}" );
AddSpawnables( Directory );
/*
var dir = gd.DirAccess.Open( Directory);
if( dir == null )
{
log.error( $"{log.loc().Log}: Unable to open directory: {Directory}" );
return;
}
dir.ListDirBegin();
while (true)
{
var fileName = dir.GetNext();
if (fileName == String.Empty)
break;
if (dir.CurrentIsDir())
continue;
var path = System.IO.Path.Combine(Directory, fileName);
log.info( $"{log.loc().Log}: Spawning scene from file: {path}" );
AddSpawnableScene( path );
/*
var scene = ResourceLoader.Load<PackedScene>( path );
if ( scene == null )
{
GD.PrintErr( $"DirSpawner: Unable to load scene: {path}" );
continue;
}
var instance = scene.Instantiate();
if ( instance == null )
{
GD.PrintErr( $"DirSpawner: Unable to instantiate scene: {path}" );
continue;
}
AddChild( instance );
* /
}
dir.ListDirEnd();
*/
}
public void AddSpawnables( string dir )
{
var files = DirAccess.GetFilesAt( dir );
foreach( var file in files )
{
var path = System.IO.Path.Combine( dir, file );
log.info( $"{log.loc().Log}: Add scene: {path}" );
AddSpawnableScene( path );
}
var directories = DirAccess.GetDirectoriesAt( dir );
foreach( var subdir in directories )
{
var path = System.IO.Path.Combine( dir, subdir );
AddSpawnables( path );
}
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process( double delta )
{
}
}

View File

@ -0,0 +1 @@
uid://c4mhl04o8yvvl

View File

@ -0,0 +1,26 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2025
using Godot;
using System;
[GlobalClass]
public partial class ListSpawner : MultiplayerSpawner
{
[Export]
public Godot.Collections.Array<PackedScene> Scenes;
// 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 )
{
}
}

View File

@ -0,0 +1 @@
uid://q13vs3ukhvov

7
addons/core/plugin.cfg Normal file
View File

@ -0,0 +1,7 @@
[plugin]
name="Core"
description="A core set of classes for games in Godot"
author="marcsh"
version="0.1"
script="core.cs"

126
addons/core/prof/Prof.cs Normal file
View File

@ -0,0 +1,126 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using System;
using System.Runtime.CompilerServices;
using Godot;
namespace prof;
public record struct Id( ulong id );
public interface Profiler
{
public Id Begin( string name );
public void End( Id id );
public void Text( Id id, string text );
public void Value( Id id, ulong value );
}
public class Zone : IDisposable
{
public Zone(
[CallerFilePath] string dbgPath = "",
[CallerLineNumber] int dbgLine = 0,
[CallerMemberName] string dbgMethod = ""
)
{
var file = log.whatFile( dbgPath );
_id = Util.GetProfiler().Begin( $"{file}::{dbgMethod}" );
}
public void Dispose()
{
Util.GetProfiler().End( _id );
}
public void Text( string text )
{
Util.GetProfiler().Text( _id, text );
}
public void Value( ulong value )
{
Util.GetProfiler().Value( _id, value );
}
Id _id;
}
static public class Util
{
static public Profiler GetProfiler()
{
//*
return new ProfilerNull();
/*/
return new ProfilerTracy();
//*/
}
}
/*
public class ProfilerTracy : Profiler
{
public Id Begin(string name)
{
var id = GD.TracyBegin( name );
return new Id( id );
}
public void End(Id id)
{
GD.TracyEnd( id.id );
}
public void Text(Id id, string text)
{
GD.TracyName( id.id, text );
}
public void Value(Id id, ulong value)
{
GD.TracyValue( id.id, value );
}
}
/*/
public class ProfilerNull : Profiler
{
static Id s_Id = new Id( 0 );
public Id Begin( string name )
{
return s_Id;
}
public void End( Id id )
{
}
public void Text( Id id, string text )
{
}
public void Value( Id id, ulong value )
{
}
}
//*/

View File

@ -0,0 +1 @@
uid://k3h7t2btjskm

View File

@ -0,0 +1,19 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2025
using Godot;
using System;
/*
[GlobalClass]
public partial class DataRes : Resource, ent.Data
{
}
*/

View File

@ -0,0 +1 @@
uid://c1i5d08v3p1tp

View File

@ -0,0 +1,71 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
using System;
using Godot.Sharp.Extras;
[GlobalClass]
public partial class BattleScreen : ScreenGeneric<BattleScreenDef>
{
static public BattleScreen _ { get; private set; }
//[ExportCategory( "Initialized" )]
//[Export]
//public Battle _battle;
public override string DebugString() => $"{GetType().Name}: {Def.ResourcePath}";
public BattleScreen()
{
_ = this;
}
public override void _Ready()
{
log.info( $"{GetType().Name}._Ready::Begin with Scene {Def.Scene.ResourcePath}" );
base._Ready();
this.OnReady();
Name = $"BattleScreen";
log.info( $"Running {GetType().Name} with Scene {Def.Scene.ResourcePath}" );
// @@@ PORT Battles as a concept
/*
var scene = Def.Scene.Instantiate<BattleScene>();
AddChild( scene );
scene.StartEvent();
Callable.From(
() => {
var path = Def.BattleDataPath.Replace( "res://", "" );
var battleData = new res.Ref<cs.BattleData>( path );
_battle = Battle.CreateBattle( this, scene, battleData, $"{Def.BattleDataPath}" );
} ).CallDeferred();
*/
log.info( $"{GetType().Name}._Ready::End with Scene {Def.Scene.ResourcePath}" );
}
public override void _Process( double delta )
{
base._Process( delta );
}
}

View File

@ -0,0 +1 @@
uid://n1qmhlxxri83

View File

@ -0,0 +1,22 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
using System;
[GlobalClass]
public partial class BattleScreenDef : ScreenGenericDef<BattleScreenDef, BattleScreen>
{
[Export]
public PackedScene Scene { get; set; }
[Export( PropertyHint.File )]
public string BattleDataPath;
}

View File

@ -0,0 +1 @@
uid://c05ywtujy7fdp

View File

@ -0,0 +1,29 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
using Godot.Sharp.Extras;
using System;
[GlobalClass]
public partial class NilScreen : ScreenGeneric<NilScreenDef>
{
static int s_count = 1024;
public override string DebugString() => $"{GetType().Name}:{Name} {s_count}";
public override void _Ready()
{
base._Ready();
this.OnReady();
Name = $"Nil {s_count++}";
}
}

View File

@ -0,0 +1 @@
uid://dry64hhed8cf3

View File

@ -0,0 +1,15 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
[GlobalClass]
public partial class NilScreenDef : ScreenGenericDef<NilScreenDef, NilScreen>
{
}

View File

@ -0,0 +1 @@
uid://dwj6x4da1vtnv

View File

@ -0,0 +1,20 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
using System;
using System.ComponentModel;
[GlobalClass]
public partial class Screen : Node
{
public virtual ScreenDef Def { get; }
public virtual string DebugString() => $"NEED OVERRIDE {GetType().Name}: {Def.ResourcePath}";
}

View File

@ -0,0 +1 @@
uid://cpcorxlhh1sry

View File

@ -0,0 +1,23 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
[GlobalClass]
public partial class ScreenDef : Resource
{
[Export]
public double TransitionTime = 1.0;
public Func<Screen> FnCreate = () => new Screen();
//public Func<ScreenDef, Event<ScreenDef>> FnCreate = new Func<ScreenDef, Event<ScreenDef>>( ( def ) => { return new Event<ScreenDef>() { Def = def }; } );
}

View File

@ -0,0 +1 @@
uid://ckhijop6db37v

View File

@ -0,0 +1,40 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
public partial class ScreenGeneric<TDEF> : Screen
where TDEF : ScreenDef
{
public ScreenGeneric()
{
}
public ScreenGeneric( TDEF def )
{
Def = def;
}
new public TDEF Def { get; set; }
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
base._Ready();
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process( double delta )
{
}
}

View File

@ -0,0 +1 @@
uid://b3pganeh6vkvo

View File

@ -0,0 +1,23 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
using Microsoft.CodeAnalysis.CSharp.Syntax;
public partial class ScreenGenericDef<TSUBDEF, TEVT> : ScreenDef
where TSUBDEF : ScreenDef
where TEVT : ScreenGeneric<TSUBDEF>, new()
{
public ScreenGenericDef()
{
FnCreate = () => new TEVT { Def = this as TSUBDEF };
}
}

View File

@ -0,0 +1 @@
uid://duyk67wxc2dtu

View File

@ -0,0 +1,29 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
public partial class ScreenScene3D : Node3D
{
public void FreeEvent()
{
var parent = GetParent();
parent.QueueFree();
}
virtual public void StartEvent()
{
}
public void StopEvent()
{
ProcessMode = ProcessModeEnum.Disabled;
}
}

View File

@ -0,0 +1 @@
uid://uvkoj8pnfvbn

View File

@ -0,0 +1,30 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
using System;
public partial class ScreenScene : Control
{
public void FreeEvent()
{
var parent = GetParent();
parent.QueueFree();
}
public void StartEvent()
{
}
public void StopEvent()
{
ProcessMode = ProcessModeEnum.Disabled;
}
}

View File

@ -0,0 +1 @@
uid://c60otp8j7cj41

View File

@ -0,0 +1,34 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2025
using Godot;
using Godot.Sharp.Extras;
using System;
[GlobalClass]
public partial class SimpleScreen : ScreenGeneric<SimpleScreenDef>
{
public override string DebugString() => $"{GetType().Name}(base): {Def.ResourcePath}";
public override void _Ready()
{
base._Ready();
this.OnReady();
Name = $"SimpleScreen";
log.info( $"Running SimpleScreen with Scene {Def.Scene.ResourcePath}" );
var scene = Def.Scene.Instantiate<ScreenScene>();
AddChild( scene );
scene.StartEvent();
}
}

View File

@ -0,0 +1 @@
uid://daiwvsui0wjvf

View File

@ -0,0 +1,21 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System;
[GlobalClass]
public partial class SimpleScreenDef : ScreenGenericDef<SimpleScreenDef, SimpleScreen>
{
[Export]
public PackedScene Scene;
}

View File

@ -0,0 +1 @@
uid://0cfmjbu3xcoa

View File

@ -0,0 +1,49 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
using System;
using Godot.Sharp.Extras;
[GlobalClass]
public partial class TraversalScreen : ScreenGeneric<TraversalScreenDef>
{
public override string DebugString() => $"{GetType().Name}: {Def.ResourcePath}";
public override void _Ready()
{
log.info( $"{GetType().Name}._Ready::Begin with Scene {Def.Scene.ResourcePath}" );
base._Ready();
this.OnReady();
Name = $"TraversalScreen";
log.info( $"Running {GetType().Name} with Scene {Def.Scene.ResourcePath}" );
var scene = Def.Scene.Instantiate<ScreenScene3D>();
AddChild( scene );
scene.StartEvent();
log.info( $"{GetType().Name}._Ready::Begin with Scene {Def.Scene.ResourcePath}" );
}
public override void _Process( double delta )
{
base._Process( delta );
}
}

View File

@ -0,0 +1 @@
uid://b5m5em4s2p16i

View File

@ -0,0 +1,27 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2025
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
using System;
[GlobalClass]
public partial class TraversalScreenDef : ScreenGenericDef<TraversalScreenDef, TraversalScreen>
{
[Export]
public PackedScene Scene;
}

View File

@ -0,0 +1 @@
uid://biy423jxrhrcw

View File

@ -0,0 +1,22 @@
[gd_scene load_steps=3 format=3 uid="uid://cwm7nrm3wcdu3"]
[ext_resource type="SystemFont" uid="uid://dy0es3e530h00" path="res://data/ui/mono_font.tres" id="1_8cvm2"]
[ext_resource type="Script" path="res://addons/core/ui/TextAnimate.cs" id="2_ugfr6"]
[node name="StatusLine" type="LineEdit"]
offset_left = 91.0
offset_top = 1135.0
offset_right = 919.0
offset_bottom = 1169.0
theme_override_fonts/font = ExtResource("1_8cvm2")
text = "//"
placeholder_text = "last_detail"
editable = false
expand_to_text_length = true
[node name="TextAnimate" type="Control" parent="."]
anchors_preset = 0
offset_right = 40.0
offset_bottom = 40.0
script = ExtResource("2_ugfr6")
Cursor = "_"

View File

@ -0,0 +1,76 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
using System;
[GlobalClass]
public partial class RichTextAnim : RichTextLabel
{
[Export]
public string TextPrefix = "";
[Export]
public string TextSuffix = "";
[Export]
public double MSPerChar = 100;
[Export]
public string OriginalText = "{BAD ERROR}";
[ExportCategory( "Animation" )]
[Export]
public int StartingOffset = 0;
[Export]
public int EndingOffset = 0;
[Export]
public int CharCount = 0;
[Export]
public double TimeTillNextChar = 0;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
OriginalText = Text;
Text = "";
TimeTillNextChar = MSPerChar / 1000.0;
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process( double delta )
{
TimeTillNextChar -= delta;
if( TimeTillNextChar <= 0 )
{
TimeTillNextChar += ( MSPerChar / 1000.0 );
if( CharCount < OriginalText.Length )
{
Text = TextPrefix + OriginalText.Substring( 0, CharCount ) + TextSuffix;
++CharCount;
}
else
{
Text = TextPrefix + OriginalText + TextSuffix;
ProcessMode = ProcessModeEnum.Disabled;
}
}
}
}

View File

@ -0,0 +1 @@
uid://bfq7roxsq3xbq

View File

@ -0,0 +1,243 @@
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// D E R E L I C T
//
/// // (c) 2003..2024
using Godot;
using Microsoft.Diagnostics.Tracing.Parsers.IIS_Trace;
using System;
/*
T O D O:
x) Add blinking cursor
D O I N G:
D O N E:
*/
[GlobalClass]
public partial class TextAnimate : Control
{
public Action<string> OnFinshed;
[Export]
public NodePath TargetPath = "..";
[Export]
public int TrimStartCount = 0;
[Export]
public int TrimeEndCount = 0;
[Export]
public double InitialDelay = 0;
[Export]
public double MSPerChar = 100;
[Export]
public string OriginalText = "{ERR_BROKEN_TextAnimate}";
[Export]
public string Cursor = "";
[ExportCategory( "Runtime" )]
[Export]
public Control TargetControl = null;
[Export]
public string TextSuffix = "";
[Export]
public int CharCount = 0;
[Export]
public double TimeTillNextChar = 0;
//Dont need prefix string since thats implicit in OriginalText
[Export]
public string SuffixStr = "";
Func<String> GetNodeText;
Action<String> SetNodeText;
// Called when the node enters the scene tree for the first time.
public override void _Ready()
{
log.var( this.Name );
//log.info($"{this?.Name}");
if( TargetPath == null || TargetPath.IsEmpty )
{
var parent = GetParent();
if( parent != null )
{
log.info( $"{Name}: No TargetPath set, using parent {parent.Name}" );
TargetPath = parent.GetPath();
}
else
{
log.warn( $"{Name}: No TargetPath set and no parent to use" );
ProcessMode = ProcessModeEnum.Disabled;
return;
}
return;
}
TargetControl = GetNode<Control>( TargetPath );
if( TargetControl == null )
{
log.warn( $"{Name}({this.NamePath()}): No node at {TargetPath}" );
return;
}
//*
if( TargetControl is LineEdit tcLineEdit )
{
log.info( $"{Name} Connect to {TargetControl.Name}'s TextChanged" );
tcLineEdit.TextChanged += ( text ) => Reset();
}
else if( TargetControl is RichTextLabel richTextLabel )
{
log.info( $"{Name} Connect to {TargetControl.Name}'s TextChanged" );
}
else if( TargetControl is Button button )
{
log.info( $"{Name} Connect to {TargetControl.Name}'s TextChanged" );
}
else
//*/
{
log.high( $"{this?.Name} NOT CONNECTED {TargetControl?.Name}'s TextChanged" );
}
//if( TargetControl is RichTextLabel rtLabel ) rtLabel.TextChanged += ( text ) => Reset();
log.info( $"{this.Name}: TargetControl is {TargetControl?.GetType().Name}" );
var propInfo = TargetControl?.GetType().GetProperty( "Text" );
var getMethod = propInfo?.GetGetMethod();
var setMethod = propInfo?.GetSetMethod();
if( getMethod == null || setMethod == null )
{
log.warn( $"{Name}: No Text property on {TargetControl?.GetType().Name}" );
return;
}
GetNodeText = (Func<String>)Delegate.CreateDelegate( typeof( Func<String> ), TargetControl, getMethod );
SetNodeText = (Action<String>)Delegate.CreateDelegate( typeof( Action<String> ), TargetControl, setMethod );
OriginalText = GetNodeText();
SuffixStr = OriginalText.Substring( OriginalText.Length - TrimeEndCount );
Reset();
//log.debug( $"D O N E {GetType().Name}._Ready" );
}
public void Reset( double delayMs = -1, string origText = "" )
{
if( ProcessMode == ProcessModeEnum.Disabled )
return;
if( GetNodeText == null || SetNodeText == null )
{
log.warn( $"{Name}: No Text property on {TargetControl?.GetType().Name}" );
return;
}
if( string.IsNullOrWhiteSpace( origText ) )
origText = GetNodeText();
InitialDelay = delayMs / 1000.0;
ProcessMode = ProcessModeEnum.Inherit;
OriginalText = origText;
if( delayMs < 0 )
{
delayMs = MSPerChar;
}
//log.debug( $"TA.Text Reset {OriginalText}" );
CharCount = TrimStartCount;
var text = OriginalText.Substring( 0, TrimStartCount );
TimeTillNextChar = delayMs / 1000.0;
SetNodeText( $"{text}{Cursor}{SuffixStr}" );
++CharCount;
}
// Called every frame. 'delta' is the elapsed time since the previous frame.
public override void _Process( double delta )
{
if( ProcessMode == ProcessModeEnum.Disabled )
return;
if( GetNodeText == null || SetNodeText == null )
{
return;
}
//log.info($"{this.Name}");
if( InitialDelay > 0 )
{
InitialDelay -= delta;
return;
}
TimeTillNextChar -= delta;
if( TimeTillNextChar <= 0 )
{
TimeTillNextChar += ( MSPerChar / 1000.0 );
if( CharCount < OriginalText.Length )
{
//SetNodeText( TextPrefix + OriginalText.Substring( 0, CharCount ) + TextSuffix );
var newText = $"{OriginalText.Substring( 0, CharCount )}{Cursor}{SuffixStr}";
SetNodeText( newText );
++CharCount;
}
else
{
SetNodeText( OriginalText );
OnFinshed?.Invoke( OriginalText );
//ProcessMode = ProcessModeEnum.Disabled;
}
}
}
}

View File

@ -0,0 +1 @@
uid://c7j5w7q4f0stt