Restructure into core/ and content/official/

This commit is contained in:
Johan Sandoval
2026-07-12 01:12:27 -05:00
parent e65c48c1e3
commit 69f2028ce3
132 changed files with 1269 additions and 1323 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,45 +0,0 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://c7ysojicqbx2v"
path="res://.godot/imported/Dartboard-Proto.glb-f1487085b0c09a3b97c6aa8be3864f01.scn"
[deps]
source_file="res://Models/Dartboard/Dartboard-Proto.glb"
dest_files=["res://.godot/imported/Dartboard-Proto.glb-f1487085b0c09a3b97c6aa8be3864f01.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

@@ -1,44 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cs381hglod4k1"
path.s3tc="res://.godot/imported/Dartboard-Proto_Green.png-0f939f85106a12f4ae54352c096222a2.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "c03f264d9d0115e28f662ed5ab27bbb2"
}
[deps]
source_file="res://Models/Dartboard/Dartboard-Proto_Green.png"
dest_files=["res://.godot/imported/Dartboard-Proto_Green.png-0f939f85106a12f4ae54352c096222a2.s3tc.ctex"]
[params]
compress/mode=2
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=true
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=0
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-45
View File
@@ -1,45 +0,0 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://6nml3641dpce"
path="res://.godot/imported/Dartboard.glb-35f0707e0e9b18915b2d48c5c14ac862.scn"
[deps]
source_file="res://Models/Dartboard/Dartboard.glb"
dest_files=["res://.godot/imported/Dartboard.glb-35f0707e0e9b18915b2d48c5c14ac862.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

-40
View File
@@ -1,40 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://blvifdtblwcxg"
path="res://.godot/imported/Cork.png-8b577bb6a5aa8a82bae563118e794e18.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Models/Dartboard/Textures/Cork.png"
dest_files=["res://.godot/imported/Cork.png-8b577bb6a5aa8a82bae563118e794e18.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 437 KiB

@@ -1,40 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dxxon6pdl5aw4"
path="res://.godot/imported/Flowers.jpg-4755265eb848ca410e8aa3e15ab13280.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Models/Dartboard/Textures/Flowers.jpg"
dest_files=["res://.godot/imported/Flowers.jpg-4755265eb848ca410e8aa3e15ab13280.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

@@ -1,40 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://l3ub5fmxsrwc"
path="res://.godot/imported/Green-Cork-Sector.png-21c4d4fd42f5a3cb24b7fd4da30825f7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Models/Dartboard/Textures/Green-Cork-Sector.png"
dest_files=["res://.godot/imported/Green-Cork-Sector.png-21c4d4fd42f5a3cb24b7fd4da30825f7.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

@@ -1,40 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c2qvwu0v2uolj"
path="res://.godot/imported/Green.png-6f1f5b0c58d2ec51c6b638c11ba4264b.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Models/Dartboard/Textures/Green.png"
dest_files=["res://.godot/imported/Green.png-6f1f5b0c58d2ec51c6b638c11ba4264b.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

@@ -1,40 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bewdyudac4bjw"
path="res://.godot/imported/Red-Black-Sector.png-243923859b1e205de71bccd1f89cdbe6.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Models/Dartboard/Textures/Red-Black-Sector.png"
dest_files=["res://.godot/imported/Red-Black-Sector.png-243923859b1e205de71bccd1f89cdbe6.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

-40
View File
@@ -1,40 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b1lclw6s66ou7"
path="res://.godot/imported/Red.png-5fe46449e66850590891dfad1c596ebf.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Models/Dartboard/Textures/Red.png"
dest_files=["res://.godot/imported/Red.png-5fe46449e66850590891dfad1c596ebf.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

@@ -1,40 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b3qw07lguggu6"
path="res://.godot/imported/Spider.png-3f1c947c2e5bcf6b7d7e259d62e6005a.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Models/Dartboard/Textures/Spider.png"
dest_files=["res://.godot/imported/Spider.png-3f1c947c2e5bcf6b7d7e259d62e6005a.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

@@ -1,40 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ccoht16d1pkr3"
path="res://.godot/imported/Stars.jpg-9cc50027cae80115a1f7344b30169970.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Models/Dartboard/Textures/Stars.jpg"
dest_files=["res://.godot/imported/Stars.jpg-9cc50027cae80115a1f7344b30169970.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 778 KiB

@@ -1,40 +0,0 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://dtamlt3msrj04"
path="res://.godot/imported/flat_tire-fire.png-934cda684e012e3a39a7204520eb1a02.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Models/Dartboard/Textures/flat_tire-fire.png"
dest_files=["res://.godot/imported/flat_tire-fire.png-934cda684e012e3a39a7204520eb1a02.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 775 KiB

Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 529 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 161 KiB

-13
View File
@@ -1,13 +0,0 @@
[gd_resource type="Resource" script_class="Modifier" format=3 uid="uid://ccyxtrth046pt"]
[ext_resource type="Script" uid="uid://duh0lxb7gdimr" path="res://Scripts/Modifier.gd" id="1_6fe4u"]
[ext_resource type="Texture2D" uid="uid://b1vf3204p4ys6" path="res://Models/Dartboard/Textures/Coffee.png" id="2_6fe4u"]
[resource]
script = ExtResource("1_6fe4u")
id = "0"
title = "Stars"
description = "Adds 40 to your score"
texture = ExtResource("2_6fe4u")
score_add = 40
metadata/_custom_type_script = "uid://duh0lxb7gdimr"
-13
View File
@@ -1,13 +0,0 @@
[gd_resource type="Resource" script_class="Modifier" format=3 uid="uid://bu11ohgune4yf"]
[ext_resource type="Script" uid="uid://duh0lxb7gdimr" path="res://Scripts/Modifier.gd" id="1_urlsx"]
[ext_resource type="Texture2D" uid="uid://dxxon6pdl5aw4" path="res://Models/Dartboard/Textures/Flowers.jpg" id="2_urlsx"]
[resource]
script = ExtResource("1_urlsx")
id = "0"
title = "Stars"
description = "Adds 40 to your score"
texture = ExtResource("2_urlsx")
score_add = 40
metadata/_custom_type_script = "uid://duh0lxb7gdimr"
-13
View File
@@ -1,13 +0,0 @@
[gd_resource type="Resource" script_class="Modifier" format=3 uid="uid://bigyh63smlbh5"]
[ext_resource type="Script" uid="uid://duh0lxb7gdimr" path="res://Scripts/Modifier.gd" id="1_oxvfs"]
[ext_resource type="Texture2D" uid="uid://ccoht16d1pkr3" path="res://Models/Dartboard/Textures/Stars.jpg" id="2_5aq3x"]
[resource]
script = ExtResource("1_oxvfs")
id = "0"
title = "Stars"
description = "Adds 40 to your score"
texture = ExtResource("2_5aq3x")
score_add = 40
metadata/_custom_type_script = "uid://duh0lxb7gdimr"
+160
View File
@@ -0,0 +1,160 @@
# DartGame
A 3D darts game where the core loop is **dragging modifiers onto board sectors** to create
compounding scoring interactions. Balatro's joker system, applied to a dartboard.
Built in **Godot 4**. Modding is a first-class concern, not an afterthought — our own
content ships through the same loader community mods will use.
---
## ⚠️ Install `git-lfs` BEFORE you clone
Clone without it and you'll get **130-byte text pointers** where your textures and models
should be. Godot will throw import errors that look completely baffling and you will lose
an hour figuring out why.
**NixOS**`configuration.nix`:
```nix
environment.systemPackages = with pkgs; [ git git-lfs ];
```
Or declaratively via Home Manager (also writes the LFS filter config, so a fresh machine is
correct with no imperative step):
```nix
programs.git = {
enable = true;
lfs.enable = true;
};
```
**Debian/Ubuntu**`sudo apt install git-lfs`
**macOS**`brew install git-lfs`
Then, once per machine:
```bash
git lfs install
```
---
## Setup
```bash
git clone <gitea-url>/DartGame.git
cd DartGame
git lfs pull
```
**Verify LFS worked.** Open `game/core/board/dartboard.glb` in a text editor. If it's
readable text starting with `version https://git-lfs...`, LFS did **not** run. Stop and fix
it before opening the project.
### Tooling
```bash
# GDScript formatter + linter
nix-shell -p gdtoolkit_4 # or: pipx install "gdtoolkit==4.*"
# Pre-commit hooks
pre-commit install
```
### Open the project
Point Godot 4 at `game/project.godot`.
---
## Where things live
| | Nextcloud | This repo |
|---|---|---|
| `.blend`, `.kra`, `.psd` | ✅ | ❌ **never** |
| `.glb`, `.png` (exported) | ❌ | ✅ (LFS) |
| Code, scenes, `.tres` | ❌ | ✅ |
| Moodboards, references | ✅ | ❌ |
Source files live in the **company Nextcloud share**, not here. They churn constantly, never
merge, and 99% of their revisions have no corresponding code change — they'd bloat LFS
history forever, on every clone.
Exported artifacts **do** live here, because they must version in lockstep with the code
that references them. If a UV layout changes and a script needs updating, those belong in
one commit.
`SOURCES.md` maps each repo artifact back to its Nextcloud source.
---
## Layout
```
game/
├── core/ ← THE ENGINE. Contains zero game content. Ever.
│ ├── board/ dartboard scene, mesh, sector logic
│ ├── dart/ dart scene, physics
│ ├── scoring/ ScorePacket pipeline
│ ├── game/ GameContext ← the mod API
│ ├── modding/ loader, registry, base classes, primitives
│ ├── ui/ modifier tray, drag-and-drop
│ └── debug/ hit_simulator.tscn ← you'll use this daily
├── content/official/ ← OUR CONTENT, loaded as a mod like any other
│ ├── skins/
│ ├── modifiers/
│ └── modes/
└── shared/ fonts, shaders
```
### The two rules
**1. Nothing in `core/` knows about any specific content.**
About to write `if modifier.id == "mjolnir"` in `core/`? Stop — you need a new primitive.
**2. `content/official/` must be deletable.**
Delete it, and the game boots to an empty state without crashing. That's the proof the mod
pipeline is real. If it can't, we don't have a mod system — we have a plugin folder.
---
## Docs
Full documentation lives in the Obsidian vault at
`Nextcloud/DartGame/documentation/`. Start with **Build Plan**.
---
## Contributing
**Branches:** `feat/`, `fix/`, `chore/` off `main`. `main` is protected and always runnable.
**Commits:** Conventional Commits.
```
feat(modding): add ModRegistry namespaced lookup
fix(scoring): ScorePacket.total() ignored final_override
```
**Before you open a PR:**
- [ ] `gdformat --check game/` passes
- [ ] `gdlint game/` passes
- [ ] Nothing in `core/` references a specific content ID
- [ ] New content is one self-contained folder
- [ ] If an asset changed: source updated in Nextcloud, `SOURCES.md` current
### ⚠️ Moving files
**Commit first.** Godot's refactoring is fragile. Move files from **inside the Godot
editor's FileSystem dock**, never your file manager, so references get fixed up.
If things break after a move or a branch switch:
```bash
rm -rf game/.godot/
```
then Project → Reload Current Project. If it's worse than that, `git restore .` — which only
works if you committed first.
---
## Status
**Pre-alpha.** Currently in Phase 0 (repo hygiene). See [`docs/build_plan.md`](docs/build_plan.md).
+65
View File
@@ -0,0 +1,65 @@
# Sources
Every binary artifact in this repo was exported from a source file in Nextcloud. This maps
one to the other.
**Nextcloud share:** `DartGame/sources/`
> ⚠️ **Keep this current.** It is the only thing linking the two halves of the project. A
> programmer looking at `dartboard.glb` has no other way to find out where it came from.
---
## Models
| Repo artifact | Nextcloud source | Last export |
|---|---|---|
| `game/core/board/dartboard.glb` | `sources/dartboard/dartboard.blend` | 2026-07-11 |
| `game/core/dart/dart.glb` | `sources/dart/dart.blend` | 2026-07-11 |
## Skins — bullseye
| Repo artifact | Nextcloud source | Last export |
|---|---|---|
| `game/content/official/skins/bullseye/neon_bulls.png` | `sources/dartboard/textures/neon_bulls.kra` | 2026-07-11 |
## Skins — sectors
| Repo artifact | Nextcloud source | Last export |
|---|---|---|
| `game/content/official/skins/sectors/neon_hiero_sector.png` | `sources/dartboard/textures/neon_hiero_sector.kra` | 2026-07-11 |
## Skins — flat tire
| Repo artifact | Nextcloud source | Last export |
|---|---|---|
| `game/content/official/skins/flat_tire/fire.png` | `sources/dartboard/textures/flat_tire.kra` | 2026-07-11 |
## Skins — flights
| Repo artifact | Nextcloud source | Last export |
|---|---|---|
| `game/content/official/skins/flights/default.png` | `sources/dart/textures/wings_default.kra` | 2026-07-11 |
---
## Fallback textures
Single default textures that ship with the mesh so the editor isn't pink. Not skins — these
live in `core/`, not `content/`.
| Repo artifact | Nextcloud source |
|---|---|
| `game/core/board/dartboard_default.png` | `sources/dartboard/textures/cork.kra` |
| `game/core/dart/dart_default.png` | `sources/dart/textures/dart_default.kra` |
---
## When you re-export
1. Export from the Nextcloud source
2. Drop the artifact into the repo path above
3. **Update the "Last export" date in this file**
4. Commit the artifact + this file together
If a row is missing, the artifact has no traceable source. Add it, or find out why it exists.
-23
View File
@@ -1,23 +0,0 @@
[gd_scene format=3 uid="uid://bswbcehkkqbk0"]
[ext_resource type="Script" uid="uid://8kk531f88oqw" path="res://Scenes/DartPhysics.gd" id="1_s1kro"]
[ext_resource type="PackedScene" uid="uid://cjq45anerhxnk" path="res://models/Dart.glb" id="2_mlvfh"]
[sub_resource type="CapsuleShape3D" id="CapsuleShape3D_xxti7"]
radius = 0.27441406
height = 2.3862305
[node name="Dart" type="Node3D" unique_id=367982453]
[node name="RigidBody3D" type="RigidBody3D" parent="." unique_id=1993396351]
transform = Transform3D(1, 0, 0, 0, 1.0000001, 0, 0, 0, 1.0000001, 0, 0, 0)
center_of_mass_mode = 1
script = ExtResource("1_s1kro")
angle_of_plane = -25.0
[node name="CollisionShape3D" type="CollisionShape3D" parent="RigidBody3D" unique_id=1030334431]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0.10033989, 0)
shape = SubResource("CapsuleShape3D_xxti7")
[node name="Dart2" parent="RigidBody3D" unique_id=1655536183 instance=ExtResource("2_mlvfh")]
transform = Transform3D(0.4, 0, 0, 0, 0.39999995, 0, 0, 0, 0.39999995, 0, -1.4526575, 0.015561234)
-161
View File
@@ -1,161 +0,0 @@
extends RigidBody3D
class_name Dart
#states
var in_flight: bool = false
var dragging: bool = false
#drag
var drag_plane: Plane
var drag_offset: Vector3 = Vector3.ZERO
var target_pos: Vector3 = Vector3.ZERO
# velocity tracking for release throw
var position_history: Array[Vector3] = []
var time_history: Array[float] = []
const HISTORY_LENGTH = 6
# drag "stiffness" tuning
@export var drag_follow_speed: float = 20.0 # how fast it chases the mouse
@export var max_drag_speed: float = 42.0 # clamp so it doesn't go insane
@export_range(-180,180) var angle_of_plane: float = 15.0
@export var throw_force_multiplier: float = 0.8 # scale raw throw velocity down if it's too fast
@export var max_throw_speed: float = 42.0 # hard cap so fast flicks don't kill the arc
@export var flight_gravity_scale: float = 4.0 # higher = more visible arc
@export var nose_align_speed: float = 1.0 # how fast dart rotates to face velocity
@export var dart_forward_axis: Vector3 = Vector3.FORWARD
func _ready():
freeze_mode = RigidBody3D.FREEZE_MODE_STATIC
freeze = true
func start_drag(camera: Camera3D, mouse_pos: Vector2, hit_point: Vector3):
dragging = true
in_flight = false
freeze = false
# keep it physics-active — don't freeze
gravity_scale = 0.0 # optional: ignore gravity while held so it doesn't sag
linear_damp = 5.0 # optional: a bit of damping for stability while dragged
angular_damp = 10.0
#drag_plane = Plane(camera.global_transform.basis.z, hit_point)
var base_normal = camera.global_transform.basis.z
var tilt = base_normal.rotated(camera.global_transform.basis.x, deg_to_rad(angle_of_plane)) # tilt adjustable around camera's right axis
drag_plane = Plane(tilt.normalized(), hit_point)
drag_offset = global_position - hit_point
target_pos = global_position #fixes a bug where dart drops until mouse is moved.
position_history.clear()
time_history.clear()
func update_drag(camera: Camera3D, mouse_pos: Vector2):
if not dragging:
return
var ray_origin = camera.project_ray_origin(mouse_pos)
var ray_dir = camera.project_ray_normal(mouse_pos)
var intersection = drag_plane.intersects_ray(ray_origin, ray_dir)
if intersection:
target_pos = intersection + drag_offset
func _align_dart_to_velocity(delta: float):
if linear_velocity.length() < 0.5:
return
var vel_dir = linear_velocity.normalized()
var current_forward = global_transform.basis * dart_forward_axis
var rotation_axis = current_forward.cross(vel_dir)
if rotation_axis.length() < 0.001:
return
rotation_axis = rotation_axis.normalized()
var angle = current_forward.angle_to(vel_dir)
var rotate_amount = min(angle, nose_align_speed * delta)
global_transform.basis = global_transform.basis.rotated(rotation_axis, rotate_amount)
global_transform.basis = global_transform.basis.orthonormalized()
func _physics_process(delta):
if dragging:
# chase the target position with velocity instead of teleporting
var to_target = target_pos - global_position
var desired_velocity = to_target * drag_follow_speed
if desired_velocity.length() > max_drag_speed:
desired_velocity = desired_velocity.normalized() * max_drag_speed
linear_velocity = desired_velocity
angular_velocity = angular_velocity.lerp(Vector3.ZERO, 0.2) # optional: damp spin while dragging
# track actual position for momentum calc on release
position_history.append(global_position)
time_history.append(Time.get_ticks_msec() / 1000.0)
if position_history.size() > HISTORY_LENGTH:
position_history.pop_front()
time_history.pop_front()
elif in_flight:
# Debug: confirm Y velocity is dropping (becoming negative) each frame
print("local +X world dir: ", global_transform.basis.x)
print("local +Y world dir: ", global_transform.basis.y)
print("local +Z world dir: ", global_transform.basis.z)
print("velocity dir: ", linear_velocity.normalized())
# Align dart nose to face velocity direction (tip points the way it's flying)
if linear_velocity.length() > 0.5:
_align_dart_to_velocity(delta)
else:
in_flight = false
angular_velocity = Vector3.ZERO
# If your dart tip points along +Z, use global_position + linear_velocity
# If it points along -Z, negate the direction below
var look_target = global_position + linear_velocity.normalized()
var up_ref = Vector3.UP
# Avoid gimbal lock when flying straight up/down
if abs(linear_velocity.normalized().dot(Vector3.UP)) > 0.99:
up_ref = Vector3.FORWARD
var target_basis = Basis.looking_at(linear_velocity.normalized(), up_ref)
global_transform.basis = global_transform.basis.slerp(
target_basis, nose_align_speed * delta
)
func release_drag():
if not dragging:
return
dragging = false
in_flight = true
gravity_scale = flight_gravity_scale
angular_damp = 0.0
linear_damp = 0.0 # reset to whatever your normal dart damping is
var velocity = calculate_throw_velocity() * throw_force_multiplier
if velocity.length() > max_throw_speed:
velocity = velocity.normalized() * max_throw_speed
linear_velocity = velocity
# angular_velocity left as-is from physics, or compute your own spin here
print("throw speed: %.2f" % velocity.length())
print("gravity_scale: ", gravity_scale)
print("linear_damp: ", linear_damp)
func calculate_throw_velocity() -> Vector3:
if position_history.size() < 2:
return linear_velocity # fallback to whatever physics already had
var start_pos = position_history[0]
var end_pos = position_history[position_history.size() - 1]
var start_time = time_history[0]
var end_time = time_history[time_history.size() - 1]
var dt = end_time - start_time
if dt <= 0.0:
return linear_velocity
return (end_pos - start_pos) / dt
-1
View File
@@ -1 +0,0 @@
uid://8kk531f88oqw
-49
View File
@@ -1,49 +0,0 @@
[gd_scene format=3 uid="uid://cqe6bo8v564dc"]
[ext_resource type="Script" uid="uid://d1i0lhibtflvq" path="res://Scripts/Main.gd" id="1_fq6v0"]
[ext_resource type="Script" uid="uid://x5kcjofb5pek" path="res://Scripts/DropCatcher.gd" id="3_qdhrm"]
[ext_resource type="Script" uid="uid://dnek55fao0l72" path="res://Scripts/ModifierTray.gd" id="5_kax6v"]
[ext_resource type="PackedScene" uid="uid://6nml3641dpce" path="res://Models/Dartboard/Dartboard.glb" id="5_thh7l"]
[node name="DartboardProto" type="Node3D" unique_id=1829621191 node_paths=PackedStringArray("dartboard_root")]
script = ExtResource("1_fq6v0")
dartboard_root = NodePath("Dartboard")
[node name="Camera3D" type="Camera3D" parent="." unique_id=393502700]
transform = Transform3D(-4.371139e-08, 0, 1, 0, 1, 0, -1, 0, -4.371139e-08, 2.6578834, 0.6292654, 0)
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1020512688]
[node name="Dartboard" parent="." unique_id=1632971704 instance=ExtResource("5_thh7l")]
transform = Transform3D(-4.371139e-08, 0, -1, 0, 1, 0, 1, 0, -4.371139e-08, 0.16272092, 1.2674973, 0)
[node name="CanvasLayer" type="CanvasLayer" parent="." unique_id=80012783]
[node name="DropCatcher" type="Control" parent="CanvasLayer" unique_id=1371111637 node_paths=PackedStringArray("camera", "main")]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("3_qdhrm")
camera = NodePath("../../Camera3D")
main = NodePath("../..")
reverse_direction = true
triple_max = 0.5
outer_max = 0.7
[node name="TextureTray" type="HBoxContainer" parent="CanvasLayer/DropCatcher" unique_id=799936408]
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="ModifierTray" type="Control" parent="CanvasLayer" unique_id=2127296352 node_paths=PackedStringArray("drop_catcher")]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("5_kax6v")
drop_catcher = NodePath("../DropCatcher")
-27
View File
@@ -1,27 +0,0 @@
extends Camera3D
var held_dart: Dart = null
func _unhandled_input(event):
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_LEFT:
if event.pressed:
try_grab_dart(event.position)
else:
if held_dart:
held_dart.release_drag()
held_dart = null
elif event is InputEventMouseMotion and held_dart:
held_dart.update_drag(self, event.position)
func try_grab_dart(mouse_pos: Vector2):
var ray_origin = project_ray_origin(mouse_pos)
var ray_dir = project_ray_normal(mouse_pos)
var ray_end = ray_origin + ray_dir * 1000.0
var space_state = get_world_3d().direct_space_state
var query = PhysicsRayQueryParameters3D.create(ray_origin, ray_end)
var result = space_state.intersect_ray(query)
if result and result.collider is Dart:
held_dart = result.collider
held_dart.start_drag(self, mouse_pos, result.position)
-5
View File
@@ -1,5 +0,0 @@
extends Node3D
func _unhandled_input(event):
if event.is_action_pressed("restart"):
get_tree().reload_current_scene()
-40
View File
@@ -1,40 +0,0 @@
[gd_scene format=3 uid="uid://doh7p84eej78c"]
[ext_resource type="Script" uid="uid://geqmah8kbko0" path="res://Scenes/main.gd" id="1_8gbba"]
[ext_resource type="Script" uid="uid://ds3uiri3oc05v" path="res://Scenes/camera_3d.gd" id="1_bo1nx"]
[ext_resource type="PackedScene" uid="uid://bswbcehkkqbk0" path="res://Scenes/Dart.tscn" id="1_jjgbg"]
[sub_resource type="BoxShape3D" id="BoxShape3D_qu08v"]
size = Vector3(1, 0.01, 1)
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_6q475"]
albedo_color = Color(0, 0.42745098, 0.05490196, 1)
[sub_resource type="BoxMesh" id="BoxMesh_xxti7"]
material = SubResource("StandardMaterial3D_6q475")
size = Vector3(1, 0.01, 1)
[node name="Main" type="Node3D" unique_id=819165214]
script = ExtResource("1_8gbba")
[node name="Camera3D" type="Camera3D" parent="." unique_id=1570426839]
transform = Transform3D(-1, 2.4096988e-08, -8.4036174e-08, 0, 0.9612618, 0.2756374, 8.742278e-08, 0.2756374, -0.9612618, 0, 5.0674033, -6.36445)
fov = 90.0
script = ExtResource("1_bo1nx")
[node name="Dart" parent="." unique_id=367982453 instance=ExtResource("1_jjgbg")]
transform = Transform3D(1, 0, 0, 0, 0.8363807, -0.5481493, 0, 0.5481492, 0.8363808, 0, 4.3246593, 0)
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="." unique_id=1891469178]
transform = Transform3D(-0.789482, 0.24223976, -0.56394875, -0.56595325, 0.06825608, 0.821607, 0.23751883, 0.9678125, 0.083209395, -46.816067, 15.942318, -50.370865)
[node name="StaticBody3D" type="StaticBody3D" parent="." unique_id=889230389]
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 63.39778)
[node name="CollisionShape3D" type="CollisionShape3D" parent="StaticBody3D" unique_id=229129629]
transform = Transform3D(100, 0, 0, 0, 100, 0, 0, 0, 100, 0, 0, 0)
shape = SubResource("BoxShape3D_qu08v")
[node name="MeshInstance3D" type="MeshInstance3D" parent="StaticBody3D/CollisionShape3D" unique_id=849495197]
mesh = SubResource("BoxMesh_xxti7")
skeleton = NodePath("")
-156
View File
@@ -1,156 +0,0 @@
extends Control
# DropCatcher.gd (manual-drag version)
# No longer uses Godot's built-in _drop_data. Instead it exposes
# paint_at_screen_pos(), which the trapezoid calls on release.
# This Control no longer needs to catch mouse events at all, so it can be
# tiny/invisible — but keep it in the tree so it can reach the camera & board.
@export var camera: Camera3D
@export var main: Node # the ROOT node (DartboardProto)
const RAY_LENGTH := 1000.0
var dartboard: MeshInstance3D
# Standard dartboard number sequence, clockwise starting from the TOP (12 o'clock).
const DART_NUMBERS := [20, 1, 18, 4, 13, 6, 10, 15, 2, 17,
3, 19, 7, 16, 8, 11, 14, 9, 12, 5]
# Calibration: rotate which sector counts as "top". If the wrong number gets
# hit, bump this by 1 until it lines up. Range 0-19.
@export var sector_offset := 0
# If your board's numbers go counter-clockwise in 3D, flip this.
@export var reverse_direction := false
# --- Radial band boundaries (fractions of outer radius, 0=center 1=rim) ---
# Tune these in the Inspector by reading the norm= prints for each ring.
@export var bull_inner_max := 0.04 # <= this = inner bull (50)
@export var bull_outer_max := 0.09 # <= this = outer bull (25)
@export var inner_max := 0.45 # <= this = inner single
@export var triple_max := 0.55 # <= this = triple ring
@export var outer_max := 0.80 # <= this = outer single; above = double
# Stores which modifier is applied to each surface index: { surface_index: Modifier }
var applied_modifiers: Dictionary = {}
func _ready() -> void:
# Don't block mouse input; the trapezoids handle their own dragging.
mouse_filter = Control.MOUSE_FILTER_IGNORE
print("DropCatcher ready. camera=", camera, " main=", main)
func _get_board() -> MeshInstance3D:
if dartboard == null and main != null and "dartboard" in main:
dartboard = main.dartboard
return dartboard
# Public: try to apply a modifier (paint its texture + store its rule) at a
# screen position. Returns true if it hit the board, false if it missed.
func apply_modifier_at_screen_pos(screen_pos: Vector2, modifier: Resource) -> bool:
var board := _get_board()
if board == null:
push_error("DropCatcher: no dartboard mesh (is 'main' set?).")
return false
if camera == null:
push_error("DropCatcher: camera export not set.")
return false
if modifier == null:
return false
var ray_origin := camera.project_ray_origin(screen_pos)
var ray_dir := camera.project_ray_normal(screen_pos)
var ray_end := ray_origin + ray_dir * RAY_LENGTH
var space_state := get_viewport().world_3d.direct_space_state
var query := PhysicsRayQueryParameters3D.create(ray_origin, ray_end)
query.collide_with_areas = false
query.collide_with_bodies = true
var result := space_state.intersect_ray(query)
if result.is_empty():
print(" drop missed the board")
return false
var surface_index := _surface_from_hit(result, board)
if surface_index < 0:
return false
_paint_surface(board, surface_index, modifier.texture)
applied_modifiers[surface_index] = modifier # remember the scoring rule
print(" applied '", modifier.id, "' to surface ", surface_index)
return true
# Call this when a dart lands on a surface to get the modified score.
func score_for_surface(surface_index: int, base_value: int) -> int:
if applied_modifiers.has(surface_index):
return applied_modifiers[surface_index].apply_score(base_value)
return base_value
func _surface_from_hit(result: Dictionary, board: MeshInstance3D) -> int:
var hit_point: Vector3 = result.get("position", Vector3.ZERO)
var local := board.to_local(hit_point)
var aabb := board.get_aabb()
var center := aabb.position + aabb.size * 0.5
# Board disc lies in the X/Y plane (thin on Z). Measure radius/angle in X/Y.
var dx := local.x - center.x
var dy := local.y - center.y
var radius := Vector2(dx, dy).length()
var outer_radius : float = max(aabb.size.x, aabb.size.y) * 0.5
var norm := radius / outer_radius if outer_radius > 0.0 else 0.0
print(" local=", local, " dx=", "%.3f" % dx, " dy=", "%.3f" % dy, " norm=", "%.3f" % norm)
# --- RADIAL BAND ---
# These thresholds are fractions of the outer radius. TUNE to match your art:
# the real proportions of a dartboard, from center outward.
var seg_name := ""
if norm <= bull_inner_max:
seg_name = "bull_inner" # inner bull (50)
elif norm <= bull_outer_max:
seg_name = "bull_outer" # outer bull (25)
elif norm > 1.0:
print(" outside board")
return -1
else:
# --- ANGULAR SECTOR ---
# atan2 gives angle of the hit point. We map it to one of 20 sectors,
# then index into the dartboard number sequence.
var angle := atan2(dx, dy) # -PI..PI, 0 = straight up (+y = top)
# Convert to 0..1 going clockwise from top. Adjust the rotation so 0 = top.
var t := angle / (2.0 * PI)
t = fposmod(t, 1.0)
if reverse_direction:
t = 1.0 - t
var sector_index := int(floor(t * 20.0 + 0.5)) % 20
sector_index = (sector_index + sector_offset) % 20
var number : int = DART_NUMBERS[sector_index]
# Which ring band within the sector.
var band := ""
if norm <= inner_max:
band = "inner" # inner single
elif norm <= triple_max:
band = "triple" # triple ring
elif norm <= outer_max:
band = "outer" # outer single
else:
band = "double" # double ring
seg_name = "seg_%d_%s" % [number, band]
print(" norm=", "%.3f" % norm, " -> ", seg_name)
return _index_of_surface_named(board, seg_name)
func _index_of_surface_named(board: MeshInstance3D, target_name: String) -> int:
var mesh := board.mesh
for i in mesh.get_surface_count():
var mat := mesh.surface_get_material(i)
if mat and mat.resource_name == target_name:
return i
return -1
func _paint_surface(board: MeshInstance3D, surface_index: int, tex: Texture2D) -> void:
# Always build a fresh StandardMaterial3D so the texture shows as an IMAGE,
# not tinted by the old base color. If the segment looks like a flat color
# instead of the pattern, the mesh is missing proper UVs on that face.
var new_mat := StandardMaterial3D.new()
new_mat.albedo_color = Color.WHITE # white so texture isn't tinted
new_mat.albedo_texture = tex
new_mat.texture_filter = BaseMaterial3D.TEXTURE_FILTER_LINEAR
new_mat.shading_mode = BaseMaterial3D.SHADING_MODE_UNSHADED
board.set_surface_override_material(surface_index, new_mat)
-61
View File
@@ -1,61 +0,0 @@
extends Node3D
# Attach to the ROOT node (DartboardProto).
# Leave `dartboard_root` empty to auto-search, or drag the glb node in.
@export var dartboard_root: Node
var dartboard: MeshInstance3D
func _ready() -> void:
dartboard = _find_mesh_instance(dartboard_root if dartboard_root else self)
if dartboard == null:
push_error("Could not find a MeshInstance3D. Check that the glb is in the scene.")
return
print("Found dartboard mesh node: ", dartboard.name)
_print_surfaces()
_ensure_collision()
func _find_mesh_instance(node: Node) -> MeshInstance3D:
if node is MeshInstance3D and node.mesh != null:
return node
for child in node.get_children():
var found := _find_mesh_instance(child)
if found:
return found
return null
func _print_surfaces() -> void:
var mesh := dartboard.mesh
print("=== Dartboard surfaces ===")
for i in mesh.get_surface_count():
var mat := mesh.surface_get_material(i)
var mat_name := mat.resource_name if mat else "(no material)"
print("Surface ", i, " material name: '", mat_name, "'")
print("==========================")
var aabb := dartboard.get_aabb()
print("Board local size (x,y,z): ", aabb.size)
# CHEAP collision: one box that matches the board's bounding box.
# This replaces create_trimesh_collision(), which was making it run at ~3 FPS
# because it built a collision triangle for every face of the board.
func _ensure_collision() -> void:
# Remove any existing auto-added body so we don't stack shapes on re-runs.
for child in dartboard.get_children():
if child is StaticBody3D and child.name == "AutoBoardBody":
child.queue_free()
var body := StaticBody3D.new()
body.name = "AutoBoardBody"
var col := CollisionShape3D.new()
var box := BoxShape3D.new()
# Match the mesh's bounding box. Use the AABB size directly as the box size.
var aabb := dartboard.get_aabb()
box.size = aabb.size
col.shape = box
# Center the box on the mesh's bounding-box center (not always the origin).
col.position = aabb.position + aabb.size * 0.5
body.add_child(col)
dartboard.add_child(body)
print("Created cheap box collision for the dartboard.")
-1
View File
@@ -1 +0,0 @@
uid://d1i0lhibtflvq
-84
View File
@@ -1,84 +0,0 @@
extends Node
# ModifierCatalog.gd
# Holds the master list of every modifier in the game and picks random sets
# for each round. Register this as an AUTOLOAD (Project > Project Settings >
# Autoload) with the name "ModifierCatalog" so any script can reach it globally.
#
# Two ways to fill the catalog:
# A) Drop .tres Modifier files into res://Modifiers/ and they auto-load (below).
# B) Build them in code in _register_builtins() for quick prototyping.
var modifiers: Array[Modifier] = []
var _by_id: Dictionary = {} # id -> Modifier, for fast lookup
func _ready() -> void:
_load_from_folder("res://Modifiers/")
if modifiers.is_empty():
# Fallback so the prototype works before you author .tres files.
_register_builtins()
_reindex()
print("ModifierCatalog loaded ", modifiers.size(), " modifiers.")
# Load every .tres Modifier resource from a folder.
func _load_from_folder(path: String) -> void:
var dir := DirAccess.open(path)
if dir == null:
return
dir.list_dir_begin()
var fname := dir.get_next()
while fname != "":
if not dir.current_is_dir() and fname.ends_with(".tres"):
var res := load(path + fname)
if res is Modifier:
modifiers.append(res)
fname = dir.get_next()
dir.list_dir_end()
# Quick code-defined modifiers for prototyping. Replace texture paths with yours.
func _register_builtins() -> void:
modifiers.append(_make("stars_x5", "Stars", "Multiplies this segment's value by 5.",
"res://Models/Dartboard/Dartboard-Proto_Red.png", 5.0, 0))
modifiers.append(_make("double", "Double", "Doubles this segment's value.",
"res://Models/Dartboard/Dartboard-Proto_Green.png", 2.0, 0))
modifiers.append(_make("plus10", "Bonus +10", "Adds 10 points to this segment.",
"res://Models/Dartboard/Dartboard-Proto_Cork.png", 1.0, 10))
func _make(id: String, title: String, desc: String, tex_path: String,
mult: float, add: int) -> Modifier:
var m := Modifier.new()
m.id = id
m.title = title
m.description = desc
m.texture = load(tex_path)
m.score_mult = mult
m.score_add = add
return m
func _reindex() -> void:
_by_id.clear()
for m in modifiers:
_by_id[m.id] = m
func get_by_id(id: String) -> Modifier:
return _by_id.get(id)
# Pick `count` distinct random modifiers, respecting weight. Returns an Array.
func pick_random(count: int) -> Array[Modifier]:
var pool := modifiers.duplicate()
var chosen: Array[Modifier] = []
count = min(count, pool.size())
for _i in count:
var total := 0.0
for m in pool:
total += max(m.weight, 0.0001)
var r := randf() * total
var acc := 0.0
var picked_index := 0
for j in pool.size():
acc += max(pool[j].weight, 0.0001)
if r <= acc:
picked_index = j
break
chosen.append(pool[picked_index])
pool.remove_at(picked_index) # distinct: don't pick the same one twice
return chosen
-15
View File
@@ -1,15 +0,0 @@
extends Camera3D
const RAY_LENGTH = 1000
func ray_cast():
var space_state = get_world_3d().direct_space_state
var cam = $"."
var mousepos = get_viewport().get_mouse_position()
var origin = cam.project_ray_origin(mousepos)
var end = origin + cam.project_ray_normal(mousepos) * RAY_LENGTH
var query = PhysicsRayQueryParameters3D.create(origin,end)
query.collide_with_areas = true
var result = space_state.intersect_ray(query)
result = result.get("collider")
return result
-1
View File
@@ -1 +0,0 @@
uid://ckbdoamrm313u
-50
View File
@@ -1,50 +0,0 @@
extends Node3D
var draggingCollider
var mousePosition
var doDrag = false
func _input(event):
var intersect
if event is InputEventMouse:
intersect = get_mouse_intersect(event.position)
if intersect: mousePosition = intersect.position
#snap on collider
#if intersect: mousePosition = intersect.collider.global_position
if event is InputEventMouseButton:
var leftButtonPressed = event.button_index == MOUSE_BUTTON_LEFT && event.pressed
var leftButtonReleased = event.button_index == MOUSE_BUTTON_LEFT && !event.pressed
if leftButtonReleased:
doDrag = false
drag_and_drop(intersect)
elif leftButtonPressed:
doDrag = true
drag_and_drop(intersect)
func _process(delta):
if draggingCollider:
draggingCollider.global_position = mousePosition
func drag_and_drop(intersect):
var canMove = intersect.collider in get_tree().get_nodes_in_group("Dart")
if !draggingCollider && doDrag && canMove:
draggingCollider = intersect.collider
elif draggingCollider:
draggingCollider = null
func get_mouse_intersect(mousePosition):
var currentCamera = get_viewport().get_camera_3d()
var params = PhysicsRayQueryParameters3D.new()
params.from = currentCamera.project_ray_origin(mousePosition)
params.to = currentCamera.project_position(mousePosition, 1000)
if draggingCollider: params.exclude = [draggingCollider]
var worldspace = get_world_3d().direct_space_state
var result = worldspace.intersect_ray(params)
return result
-16
View File
@@ -1,16 +0,0 @@
extends MeshInstance3D
@onready var camera = get_node("Camera3D")
@onready var area = get_node("../../../Dart")
func _input(event):
if Input.is_action_just_pressed("leftClick"):
print(camera.ray_cast().position)
if camera.ray_cast() != null and Input.is_action_pressed("midleclic"):
var mousepos = get_viewport().get_mouse_position()
var origin = camera.projectray_origin(mousepos)
var end = camera.project_ray_normal(mousepos)
var depth = origin.distence_to(area.global_position)
var final_position = origin + end * depth
area.global_position=final_position
-1
View File
@@ -1 +0,0 @@
uid://cc5yy1yc3j2uu
+9
View File
@@ -0,0 +1,9 @@
{
"id": "official",
"display_name": "DartGame",
"author": "Ced Games",
"version": "0.1.0",
"api_version": 1,
"depends": [],
"description": "The base game content."
}
Binary file not shown.
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://b1vf3204p4ys6"
path="res://.godot/imported/Coffee.png-7ec89259677377dfb36274d8ce0e9258.ctex"
uid="uid://bayaus0tgltb3"
path="res://.godot/imported/neon_hiero.png-401e600b4e3a81c5e49b654215a0e850.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Models/Dartboard/Textures/Coffee.png"
dest_files=["res://.godot/imported/Coffee.png-7ec89259677377dfb36274d8ce0e9258.ctex"]
source_file="res://content/official/skins/bulls/neon_hiero.png"
dest_files=["res://.godot/imported/neon_hiero.png-401e600b4e3a81c5e49b654215a0e850.ctex"]
[params]
Binary file not shown.
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://ch4b1pnokuu8d"
path="res://.godot/imported/Wings-Red-Blue.png-701faf0c1b6c3af11eb52058dfda7798.ctex"
uid="uid://b672fbyuxxktw"
path="res://.godot/imported/ocean_fire.png-b1f0d5fea08640de091cb5cf724309e6.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Models/Dart/Textures/Wings-Red-Blue.png"
dest_files=["res://.godot/imported/Wings-Red-Blue.png-701faf0c1b6c3af11eb52058dfda7798.ctex"]
source_file="res://content/official/skins/flat_tire/ocean_fire.png"
dest_files=["res://.godot/imported/ocean_fire.png-b1f0d5fea08640de091cb5cf724309e6.ctex"]
[params]
Binary file not shown.
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://c7jxj25ophbsb"
path="res://.godot/imported/Dart-Default.png-2feb9f56821a29d976ec19a662a6977f.ctex"
uid="uid://btg3hosfx1sdg"
path="res://.godot/imported/toon_purple.png-f1a192acdf1c762039bd65e86672f7e4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Models/Dart/Textures/Dart-Default.png"
dest_files=["res://.godot/imported/Dart-Default.png-2feb9f56821a29d976ec19a662a6977f.ctex"]
source_file="res://content/official/skins/flat_tire/toon_purple.png"
dest_files=["res://.godot/imported/toon_purple.png-f1a192acdf1c762039bd65e86672f7e4.ctex"]
[params]
Binary file not shown.
@@ -2,16 +2,16 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://bhbtrcw8xbp1g"
path="res://.godot/imported/Wings-Default.png-b4a7a92ebd237d86daaae21adc10d873.ctex"
uid="uid://brqn2ve8pm3i8"
path="res://.godot/imported/neon_hiero_black.png-7709dfdedde53a261eb17c86736a737f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://Models/Dart/Textures/Wings-Default.png"
dest_files=["res://.godot/imported/Wings-Default.png-b4a7a92ebd237d86daaae21adc10d873.ctex"]
source_file="res://content/official/skins/sectors/neon_hiero_black.png"
dest_files=["res://.godot/imported/neon_hiero_black.png-7709dfdedde53a261eb17c86736a737f.ctex"]
[params]
Binary file not shown.
@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://bataamlsyyup6"
path="res://.godot/imported/neon_hiero_white.png-c93ce5ab952a3d2618c297fc1cae11f4.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://content/official/skins/sectors/neon_hiero_white.png"
dest_files=["res://.godot/imported/neon_hiero_white.png-c93ce5ab952a3d2618c297fc1cae11f4.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
+173
View File
@@ -0,0 +1,173 @@
class_name Dartboard
extends StaticBody3D
## The board. 21 material slots: sector_1 .. sector_20, plus "bulls".
##
## Owns the face -> surface -> slot mapping, which is what makes hit detection
## survive the board moving, rotating, or having its sectors remapped. Ask the
## geometry which sector was hit; don't calculate it from an angle.
## Every slot on the board, in the order Blender exported them (order doesn't
## matter — we index by name).
const BULL_SLOT := &"bulls"
@onready var mesh_instance: MeshInstance3D = $Model/dartboard
## StringName -> surface index
var _slot_to_surface: Dictionary = {}
## global face index -> StringName
var _face_to_slot: Dictionary = {}
var _outer_radius: float = 0.0
func _ready() -> void:
_index_slots()
_index_faces()
_cache_radius()
# --- Indexing -----------------------------------------------------------
func _index_slots() -> void:
var mesh := mesh_instance.mesh
if mesh == null:
push_error("Dartboard has no mesh.")
return
for i in mesh.get_surface_count():
var mat := mesh.surface_get_material(i)
if mat == null:
push_warning("Surface %d has no material — can't be skinned or hit." % i)
continue
var slot := StringName(mat.resource_name)
if slot == &"":
push_warning("Surface %d's material has no name." % i)
continue
_slot_to_surface[slot] = i
print("Dartboard: indexed %d slots." % _slot_to_surface.size())
if _slot_to_surface.size() != 21:
push_warning("Expected 21 slots (20 sectors + bulls), got %d." % _slot_to_surface.size())
## Build a global-face-index -> slot map.
##
## The physics engine reports face_index as an index into the WHOLE trimesh,
## but the mesh stores faces per-surface. So we walk the surfaces in order,
## accumulating a running face count, and record which range belongs to which.
##
## This assumes the ConcavePolygonShape3D was built from the mesh with surfaces
## in the same order — which is what create_trimesh_shape() does.
func _index_faces() -> void:
var mesh := mesh_instance.mesh
if mesh == null:
return
var running := 0
for i in mesh.get_surface_count():
var arrays := mesh.surface_get_arrays(i)
if arrays.is_empty():
continue
var indices: PackedInt32Array = arrays[Mesh.ARRAY_INDEX]
var verts: PackedVector3Array = arrays[Mesh.ARRAY_VERTEX]
# Triangle count: from the index buffer if present, else from vertices.
var tri_count := (indices.size() / 3) if indices.size() > 0 else (verts.size() / 3)
var mat := mesh.surface_get_material(i)
var slot := StringName(mat.resource_name) if mat else &""
for f in tri_count:
_face_to_slot[running + f] = slot
running += tri_count
print("Dartboard: mapped %d faces." % _face_to_slot.size())
func _cache_radius() -> void:
var aabb := mesh_instance.get_aabb()
# The disc's two large axes. The third is the board's thickness.
var sizes := [aabb.size.x, aabb.size.y, aabb.size.z]
sizes.sort()
_outer_radius = sizes[2] * 0.5 # largest dimension = diameter
# --- Public -------------------------------------------------------------
## Which slot does this trimesh face belong to?
func slot_for_face(face_index: int) -> StringName:
return _face_to_slot.get(face_index, &"")
## Paint a texture onto one slot. Used for both skins and modifier overlays.
func paint_slot(slot: StringName, texture: Texture2D) -> void:
if not _slot_to_surface.has(slot):
push_warning("No slot named '%s'." % slot)
return
var surface: int = _slot_to_surface[slot]
var mat := mesh_instance.get_active_material(surface)
if mat == null:
return
# Duplicate, or we mutate the shared material from the .glb — which leaks
# to every instance AND can get written back to disk in the editor.
var unique := mat.duplicate() as StandardMaterial3D
unique.albedo_texture = texture
unique.albedo_color = Color.WHITE # don't tint the texture
mesh_instance.set_surface_override_material(surface, unique)
## Reset a slot to its imported material.
func clear_slot(slot: StringName) -> void:
if not _slot_to_surface.has(slot):
return
mesh_instance.set_surface_override_material(_slot_to_surface[slot], null)
func get_slots() -> Array[StringName]:
var out: Array[StringName] = []
out.assign(_slot_to_surface.keys())
return out
## Board radius in local units.
func outer_radius() -> float:
return _outer_radius
## Project a LOCAL point onto the board's face plane, returning 2D coords
## where +Y is "up" on the board (toward the 20).
##
## Which two axes those are depends on how the .glb imported. glTF is +Y up,
## so a board modeled facing +Z in Blender usually lands facing +Z here, with
## the disc in the X/Y plane. If sector detection is 90 degrees off, this is
## the function to fix.
func planar_coords(local_point: Vector3) -> Vector2:
var aabb := mesh_instance.get_aabb()
var center := aabb.position + aabb.size * 0.5
# Identify the thin axis — that's the board's normal.
var s := aabb.size
if s.z <= s.x and s.z <= s.y:
return Vector2(local_point.x - center.x, local_point.y - center.y)
elif s.y <= s.x and s.y <= s.z:
return Vector2(local_point.x - center.x, local_point.z - center.z)
else:
return Vector2(local_point.z - center.z, local_point.y - center.y)
## Debug: what did Blender actually export?
func print_slots() -> void:
var mesh := mesh_instance.mesh
print("=== Dartboard slots ===")
for i in mesh.get_surface_count():
var mat := mesh.surface_get_material(i)
print(" surface %2d: '%s'" % [i, mat.resource_name if mat else "(none)"])
print(" radius: %.4f aabb: %s" % [_outer_radius, mesh_instance.get_aabb().size])
print("=======================")
+1
View File
@@ -0,0 +1 @@
uid://cme5xqn2hlrlu
Binary file not shown.
@@ -3,13 +3,13 @@
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://cjq45anerhxnk"
path="res://.godot/imported/Dart.glb-e68d17366fac688e4c81cf4cc62e1175.scn"
uid="uid://djr5vbnb652ax"
path="res://.godot/imported/dartboard.glb-8db0957180c245aae0575b1ad098f883.scn"
[deps]
source_file="res://models/Dart.glb"
dest_files=["res://.godot/imported/Dart.glb-e68d17366fac688e4c81cf4cc62e1175.scn"]
source_file="res://core/board/dartboard.glb"
dest_files=["res://.godot/imported/dartboard.glb-8db0957180c245aae0575b1ad098f883.scn"]
[params]
+27
View File
@@ -0,0 +1,27 @@
[gd_scene format=3 uid="uid://dojbd5wl7ntp0"]
[ext_resource type="Script" uid="uid://cme5xqn2hlrlu" path="res://core/board/dartboard.gd" id="1"]
[ext_resource type="PackedScene" uid="uid://djr5vbnb652ax" path="res://core/board/dartboard.glb" id="2"]
[ext_resource type="Shape3D" uid="uid://vr03k1qlo10y" path="res://core/board/dartboard_collision.res" id="3_6aacy"]
[ext_resource type="Shape3D" uid="uid://c0vqtoe8q7yly" path="res://core/board/spider_collision.res" id="4_khw4q"]
[sub_resource type="PhysicsMaterial" id="PhysicsMaterial_uomu6"]
friction = 0.15
bounce = 0.6
[node name="Dartboard" type="StaticBody3D" unique_id=838863701]
script = ExtResource("1")
[node name="Model" parent="." unique_id=1556270899 instance=ExtResource("2")]
[node name="CollisionShape3D" type="CollisionShape3D" parent="." unique_id=1180985747]
shape = ExtResource("3_6aacy")
[node name="SpiderBody" type="StaticBody3D" parent="." unique_id=216948518]
collision_layer = 8
physics_material_override = SubResource("PhysicsMaterial_uomu6")
[node name="CollisionShape3D" type="CollisionShape3D" parent="SpiderBody" unique_id=1618627145]
shape = ExtResource("4_khw4q")
[editable path="Model"]
Binary file not shown.
@@ -2,20 +2,20 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://bobnv6cyi7dc6"
path.s3tc="res://.godot/imported/Dart_Dart-Default.png-4bd6068152c7966988ca2c23bd03ba76.s3tc.ctex"
uid="uid://dj6wb70lh5s4f"
path.s3tc="res://.godot/imported/dartboard_bulls.png-354417d2f083e713950eed8be5c54f73.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "9c4718c8d2830abe8cc9971ef621b46a"
"md5": "ce1804f3e86fea26503097d5e278c919"
}
[deps]
source_file="res://Models/Dart/Dart_Dart-Default.png"
dest_files=["res://.godot/imported/Dart_Dart-Default.png-4bd6068152c7966988ca2c23bd03ba76.s3tc.ctex"]
source_file="res://core/board/dartboard_bulls.png"
dest_files=["res://.godot/imported/dartboard_bulls.png-354417d2f083e713950eed8be5c54f73.s3tc.ctex"]
[params]
Binary file not shown.
Binary file not shown.
@@ -2,20 +2,20 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://bqxi7x76uv4tq"
path.s3tc="res://.godot/imported/Dart_Wings-Default.png-358040735cbc551a0d2abb04506c708a.s3tc.ctex"
uid="uid://da2elnsphlc5i"
path.s3tc="res://.godot/imported/dartboard_flat_tire.png-97f4bd426c06f71b009cd942664be6fa.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "52ee9a89e7c6ed5f537103a41115551e"
"md5": "d24ebe44ff702bdbd5528c9d29427568"
}
[deps]
source_file="res://Models/Dart/Dart_Wings-Default.png"
dest_files=["res://.godot/imported/Dart_Wings-Default.png-358040735cbc551a0d2abb04506c708a.s3tc.ctex"]
source_file="res://core/board/dartboard_flat_tire.png"
dest_files=["res://.godot/imported/dartboard_flat_tire.png-97f4bd426c06f71b009cd942664be6fa.s3tc.ctex"]
[params]
Binary file not shown.
@@ -2,20 +2,20 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://dk6gnbvgumq7c"
path.s3tc="res://.godot/imported/Dartboard-Proto_Cork.png-9700f4c0b675dee65dc4e1c14aa931b3.s3tc.ctex"
uid="uid://cjw6x7ig1ogbo"
path.s3tc="res://.godot/imported/dartboard_sector_black.png-39f98bf69c481faad6b8976a0994ecb5.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "5b186b11689f911ad6691488292c3475"
"md5": "831beecb2a48df4a1f6850e3e8c938dd"
}
[deps]
source_file="res://Models/Dartboard/Dartboard-Proto_Cork.png"
dest_files=["res://.godot/imported/Dartboard-Proto_Cork.png-9700f4c0b675dee65dc4e1c14aa931b3.s3tc.ctex"]
source_file="res://core/board/dartboard_sector_black.png"
dest_files=["res://.godot/imported/dartboard_sector_black.png-39f98bf69c481faad6b8976a0994ecb5.s3tc.ctex"]
[params]
Binary file not shown.
@@ -2,20 +2,20 @@
importer="texture"
type="CompressedTexture2D"
uid="uid://cvv4akpwsudu3"
path.s3tc="res://.godot/imported/Dartboard-Proto_Red.png-e757930c4a42a8dc67547b3409641a91.s3tc.ctex"
uid="uid://cry28pcmt01gk"
path.s3tc="res://.godot/imported/dartboard_sector_white.png-6dbf28d122f3327d568bcbb29e7f70a0.s3tc.ctex"
metadata={
"imported_formats": ["s3tc_bptc"],
"vram_texture": true
}
generator_parameters={
"md5": "e46ac968aab4da0b0c65bc59957f2f96"
"md5": "aa25e4080cfa45ac8064cbf217904fb3"
}
[deps]
source_file="res://Models/Dartboard/Dartboard-Proto_Red.png"
dest_files=["res://.godot/imported/Dartboard-Proto_Red.png-e757930c4a42a8dc67547b3409641a91.s3tc.ctex"]
source_file="res://core/board/dartboard_sector_white.png"
dest_files=["res://.godot/imported/dartboard_sector_white.png-6dbf28d122f3327d568bcbb29e7f70a0.s3tc.ctex"]
[params]
Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More