Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions compile.bat
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ if %RCCE%==1 (
"%BLITZPATH%\bin\blitzcc.exe" -o "%ROOTDIR%\bin\Server.exe" "%ROOTDIR%\src\Server.bb" || (cd /d "%ROOTDIR%" & endlocal & exit /b 1)
"%BLITZPATH%\bin\blitzcc.exe" -o "%ROOTDIR%\Project Manager.exe" -n "%ROOTDIR%\res\Icon.ico" "%ROOTDIR%\src\Project Manager.bb" || (cd /d "%ROOTDIR%" & endlocal & exit /b 1)
"%BLITZPATH%\bin\blitzcc.exe" -o "%ROOTDIR%\bin\GUE.exe" -n "%ROOTDIR%\res\Icon.ico" "%ROOTDIR%\src\GUE.bb" || (cd /d "%ROOTDIR%" & endlocal & exit /b 1)
"%BLITZPATH%\bin\blitzcc.exe" -o "%ROOTDIR%\bin\Loom.exe" -n "%ROOTDIR%\res\Icon.ico" "%ROOTDIR%\src\Loom.bb" || (cd /d "%ROOTDIR%" & endlocal & exit /b 1)
"%BLITZPATH%\bin\blitzcc.exe" -o "%ROOTDIR%\bin\Client.exe" -n "%ROOTDIR%\res\Icon.ico" "%ROOTDIR%\src\Client.bb" || (cd /d "%ROOTDIR%" & endlocal & exit /b 1)
)

Expand Down
2 changes: 2 additions & 0 deletions compile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ if [[ "${RCCE}" -eq 1 ]]; then
"${ROOTDIR}/src/Project Manager.bb"
"${BLITZCC}" -o "${ROOTDIR}/bin/GUE${EXE_SUFFIX}" "${ICON_FLAG[@]}" \
"${ROOTDIR}/src/GUE.bb"
"${BLITZCC}" -o "${ROOTDIR}/bin/Loom${EXE_SUFFIX}" "${ICON_FLAG[@]}" \
"${ROOTDIR}/src/Loom.bb"
"${BLITZCC}" -o "${ROOTDIR}/bin/Client${EXE_SUFFIX}" "${ICON_FLAG[@]}" \
"${ROOTDIR}/src/Client.bb"
fi
Expand Down
182 changes: 182 additions & 0 deletions src/Loom.bb
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
// =============================================================================
// Loom.bb -- Loom World Editor (Alpha)
// =============================================================================
//
// A drop-in alternative to GUE, sharing the on-disk data formats but with
// a fresh UI built around the Loom design concept (see
// .claude/skills/loom-design-brief/ and the prototype handoff bundle).
//
// Architecture overview (the multi-PR roadmap; this commit ships only #1):
//
// #1 Skeleton + theme + Project Manager launcher
// Loom.exe compiles, Project Manager launches it,
// shows a themed splash, exits cleanly. THIS COMMIT.
//
// #2 Data loading + atlas
// Loom uses GUE's existing data modules (Items.bb, Actors.bb,
// Spells.bb, ServerAreas.bb, ...) via Include. After load,
// the atlas surface lists every zone in the project.
//
// #3 World view
// Picking a zone in the atlas renders it in a 3D viewport
// using Blitz3D's engine (the same engine GUE's Zones tab uses).
// Click an entity to select it.
//
// #4 Composer
// Right-side property panel that paints the focused entity's
// data (faction, level, mesh, equipped items) using Loom theme
// primitives. Read-only for the alpha.
//
// Design intent for the alpha as a whole: "Loom can open my existing
// Realm Crafter project and let me look at my world through a different
// lens." Editing comes in beta.
// =============================================================================


// -----------------------------------------------------------------------------
// Bootstrap globals (mirrors GUE.bb's startup so the relative paths work
// identically -- both binaries live in bin/ and are launched with CWD set to
// <project>/Data/).
// -----------------------------------------------------------------------------
Global rcceVersion$ = "2.0.0"
Global componentName$ = "loom"
Global RootDir$ = "..\"

ChangeDir RootDir$


// -----------------------------------------------------------------------------
// Includes -- minimum surface for the skeleton.
//
// PR #1 deliberately does NOT include the data modules (Items, Actors,
// Spells, etc.) or F-UI. The skeleton's only job is to prove the build
// pipeline and the Project Manager hook work. PR #2 will add Logging-
// adjacent data loaders. PR #3 brings in Blitz3D's 3D pipeline for the
// world view.
// -----------------------------------------------------------------------------
Include "Modules\Logging.bb"
Include "Modules\Loom\Theme.bb"


// -----------------------------------------------------------------------------
// Graphics mode -- match GUE's window sizing so the two editors feel sibling.
// -----------------------------------------------------------------------------
Local Loom_width# = GetSystemMetrics(0) * 0.9
Local Loom_height# = GetSystemMetrics(1) * 0.8
If (Loom_width < 1280 And Loom_height < 800)
Loom_width = 1280
Loom_height = 800
EndIf

Graphics3D(Loom_width, Loom_height, 0, 2)
SetBuffer(BackBuffer())
AppTitle("Loom -- World Editor (Alpha) -- Realm Crafter " + rcceVersion$)


// -----------------------------------------------------------------------------
// Log -- written to Data\Logs\Loom Log.txt (relative to project root, the
// same place GUE writes its log).
// -----------------------------------------------------------------------------
Global LoomLog = StartLog("Loom Log", False)
WriteLog(LoomLog, "** Loom startup begins **", True, True)
WriteLog(LoomLog, "Resolution: " + Str(Loom_width) + "x" + Str(Loom_height))


// -----------------------------------------------------------------------------
// Resolve project name from the working directory. When PM launches us, CWD
// has been set to <project>/Data/ and then ChangeDir "..\" walked us up to
// <project>/. The leaf folder name is the project's display name.
// -----------------------------------------------------------------------------
Local cwd$ = CurrentDir$()
Local projectName$ = LoomGetLeafDir(cwd$)
WriteLog(LoomLog, "Project root: " + cwd$)
WriteLog(LoomLog, "Project name: " + projectName$)

LoomTheme_Init()


// -----------------------------------------------------------------------------
// Splash screen loop. Runs until Esc.
// PR #2 replaces this with the atlas as the boot surface.
// -----------------------------------------------------------------------------
WriteLog(LoomLog, "** Splash loop running **")

Repeat
Cls
LoomRenderSplash(Loom_width, Loom_height, projectName$)
Flip
Until KeyHit(1)

WriteLog(LoomLog, "** Loom shutdown **")
CloseAllLogs()
End


// =============================================================================
// LoomRenderSplash -- paint the alpha splash surface.
//
// Layout:
// - Full-screen vertical gradient stone_900 -> stone_950
// - Centered "LOOM" title in parchment
// - "WORLD EDITOR" subtitle in brass, spaced
// - Brass divider rule
// - Project context line
// - Footer instruction
// =============================================================================
Function LoomRenderSplash(sw, sh, projectName$)
// Background gradient (BlitzForge does not support line continuation,
// so these calls are intentionally long single lines.)
LoomGradientV(0, 0, sw, sh, LOOM_STONE_900_R, LOOM_STONE_900_G, LOOM_STONE_900_B, LOOM_STONE_950_R, LOOM_STONE_950_G, LOOM_STONE_950_B)

Local cx = sw / 2
Local cy = sh / 2

// Title -- "LOOM" centered, drawn twice with a 1px offset to fake bolder
// weight on top of the Blitz default font. Real display fonts arrive in
// a later PR.
LoomTextCentered(cx, cy - 90, "LOOM", LOOM_PARCHMENT_100_R, LOOM_PARCHMENT_100_G, LOOM_PARCHMENT_100_B)
LoomTextCentered(cx + 1, cy - 90, "LOOM", LOOM_PARCHMENT_100_R, LOOM_PARCHMENT_100_G, LOOM_PARCHMENT_100_B)

// Subtitle
LoomTextCentered(cx, cy - 64, "W O R L D E D I T O R", LOOM_BRASS_500_R, LOOM_BRASS_500_G, LOOM_BRASS_500_B)

// Brass divider (triple rule for an ornamented bar)
LoomHRule(cx - 180, cy - 40, 360, LOOM_BRASS_700_R, LOOM_BRASS_700_G, LOOM_BRASS_700_B)
LoomHRule(cx - 180, cy - 39, 360, LOOM_BRASS_500_R, LOOM_BRASS_500_G, LOOM_BRASS_500_B)
LoomHRule(cx - 180, cy - 38, 360, LOOM_BRASS_700_R, LOOM_BRASS_700_G, LOOM_BRASS_700_B)

// Project context
LoomTextCentered(cx, cy - 16, "Alpha for " + projectName$, LOOM_STONE_200_R, LOOM_STONE_200_G, LOOM_STONE_200_B)
LoomTextCentered(cx, cy + 2, "Realm Crafter Community Edition " + rcceVersion$, LOOM_STONE_300_R, LOOM_STONE_300_G, LOOM_STONE_300_B)

// Skeleton-stage notice (will be removed in PR #2 when the atlas becomes
// the boot surface).
LoomTextCentered(cx, cy + 60, "skeleton build -- atlas, world view, and composer arrive in subsequent PRs", LOOM_STONE_300_R, LOOM_STONE_300_G, LOOM_STONE_300_B)

// Footer
LoomTextCentered(cx, sh - 40, "Esc to exit", LOOM_STONE_300_R, LOOM_STONE_300_G, LOOM_STONE_300_B)
End Function


// =============================================================================
// LoomGetLeafDir -- return the leaf folder name from a directory path.
// E.g. "C:\rcce2\projects\Embergloom" -> "Embergloom".
// Falls back to the whole path if no separator is found.
// =============================================================================
Function LoomGetLeafDir$(path$)
Local trimmed$ = path$
// Strip trailing slashes / backslashes so the leaf isn't an empty string.
While Len(trimmed$) > 1 And (Right$(trimmed$, 1) = "\" Or Right$(trimmed$, 1) = "/")
trimmed$ = Left$(trimmed$, Len(trimmed$) - 1)
Wend

Local lastSep = 0
Local i = 0
For i = 1 To Len(trimmed$)
Local ch$ = Mid$(trimmed$, i, 1)
If ch$ = "\" Or ch$ = "/" Then lastSep = i
Next

If lastSep = 0 Then Return trimmed$
Return Mid$(trimmed$, lastSep + 1)
End Function
158 changes: 158 additions & 0 deletions src/Modules/Loom/Theme.bb
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// =============================================================================
// Loom/Theme.bb -- color palette and 2D drawing primitives
// =============================================================================
//
// The Loom design (see ../../design/loom-prototype/) uses a dark-fantasy
// palette pulled from three places: deep-navy night sky, stone-gray panels
// with brass trim, and parchment cream for body text. This file encodes the
// design tokens as RGB triplets, and provides drawing helpers that wrap
// Blitz3D's primitive Color/Rect/Line/Text calls so the rest of Loom can
// paint surfaces without restating colors at every site.
//
// Why custom-draw at all (vs. F-UI for everything):
// The Loom aesthetic is gradient-heavy and uses ornamented type. F-UI
// renders flat stone-gray panels with system fonts; it cannot express the
// look without a fork. Building one custom-draw layer here lets every
// surface (atlas, world view, composer, ribbon, palette) share the same
// visual language. F-UI is reserved for things it is genuinely good at
// (file dialogs, native text input).
//
// Public API (everything here is called Loom_*):
//
// Color tokens (use with LoomColor or directly):
// LOOM_STONE_950, LOOM_STONE_900, ..., LOOM_STONE_100
// LOOM_ARCANE_900 ... LOOM_ARCANE_300 (primary accent)
// LOOM_BRASS_800 ... LOOM_BRASS_300 (ornament, ranks)
// LOOM_PARCHMENT_100, LOOM_PARCHMENT_200 (body text)
// LOOM_INK_900, LOOM_INK_700 (text on parchment)
// LOOM_SUCCESS, LOOM_WARNING, LOOM_DANGER (semantic)
//
// LoomColor(r, g, b) -- thin wrapper for Blitz Color
// LoomFill(x, y, w, h, r, g, b) -- filled rectangle
// LoomBorder(x, y, w, h, r, g, b) -- 1px outline rectangle
// LoomGradientV(x, y, w, h, r1, g1, b1, r2, g2, b2)
// -- vertical gradient, top -> bottom
// LoomHRule(x, y, w, r, g, b) -- 1px horizontal divider
// LoomText(x, y, text$, r, g, b, align=0)
// -- single-line text. align: 0=left, 1=center, 2=right
// LoomTextCentered(cx, y, text$, r, g, b)
// -- text centered horizontally at cx
//
// LoomTheme_Init() -- one-time setup. Loads any fonts we need
// (current alpha: none -- uses default font).
// =============================================================================


// -----------------------------------------------------------------------------
// COLOR TOKENS -- packed RGB triplets exposed as separate r/g/b constants so
// callers can write `LoomFill(..., LOOM_STONE_900_R, LOOM_STONE_900_G,
// LOOM_STONE_900_B)`. Blitz3D's Color() takes three ints; we'd rather have
// the readable token name at every call site than a single packed int that
// has to be unpacked.
// -----------------------------------------------------------------------------

// Stone & shadow (panels, backgrounds)
Const LOOM_STONE_950_R = 8 : Const LOOM_STONE_950_G = 9 : Const LOOM_STONE_950_B = 15 // #08090f deep void
Const LOOM_STONE_900_R = 14 : Const LOOM_STONE_900_G = 16 : Const LOOM_STONE_900_B = 24 // #0e1018 canvas
Const LOOM_STONE_850_R = 20 : Const LOOM_STONE_850_G = 23 : Const LOOM_STONE_850_B = 42 // #14172a card surface
Const LOOM_STONE_800_R = 28 : Const LOOM_STONE_800_G = 34 : Const LOOM_STONE_800_B = 56 // #1c2238 raised
Const LOOM_STONE_700_R = 38 : Const LOOM_STONE_700_G = 42 : Const LOOM_STONE_700_B = 58 // #262a3a sunken
Const LOOM_STONE_500_R = 79 : Const LOOM_STONE_500_G = 75 : Const LOOM_STONE_500_B = 72 // #4f4b48 mortar
Const LOOM_STONE_300_R = 138 : Const LOOM_STONE_300_G = 133 : Const LOOM_STONE_300_B = 128 // #8a8580 dust
Const LOOM_STONE_200_R = 179 : Const LOOM_STONE_200_G = 174 : Const LOOM_STONE_200_B = 164 // #b3aea4 dry stone
Const LOOM_STONE_100_R = 216 : Const LOOM_STONE_100_G = 209 : Const LOOM_STONE_100_B = 194 // #d8d1c2 lit limestone

// Arcane blue (primary accent)
Const LOOM_ARCANE_900_R = 17 : Const LOOM_ARCANE_900_G = 38 : Const LOOM_ARCANE_900_B = 79 // #11264f
Const LOOM_ARCANE_700_R = 30 : Const LOOM_ARCANE_700_G = 80 : Const LOOM_ARCANE_700_B = 153 // #1e5099
Const LOOM_ARCANE_500_R = 61 : Const LOOM_ARCANE_500_G = 166 : Const LOOM_ARCANE_500_B = 245 // #3da6f5 signature
Const LOOM_ARCANE_300_R = 168 : Const LOOM_ARCANE_300_G = 220 : Const LOOM_ARCANE_300_B = 255 // #a8dcff

// Brass / gold (ornament)
Const LOOM_BRASS_800_R = 85 : Const LOOM_BRASS_800_G = 59 : Const LOOM_BRASS_800_B = 19 // #553b13
Const LOOM_BRASS_700_R = 122 : Const LOOM_BRASS_700_G = 88 : Const LOOM_BRASS_700_B = 33 // #7a5821
Const LOOM_BRASS_500_R = 201 : Const LOOM_BRASS_500_G = 164 : Const LOOM_BRASS_500_B = 74 // #c9a44a signature
Const LOOM_BRASS_300_R = 243 : Const LOOM_BRASS_300_G = 220 : Const LOOM_BRASS_300_B = 160 // #f3dca0

// Parchment & ink
Const LOOM_PARCHMENT_100_R = 246 : Const LOOM_PARCHMENT_100_G = 239 : Const LOOM_PARCHMENT_100_B = 220 // #f6efdc body
Const LOOM_PARCHMENT_200_R = 235 : Const LOOM_PARCHMENT_200_G = 223 : Const LOOM_PARCHMENT_200_B = 185 // #ebdfb9 muted
Const LOOM_INK_900_R = 22 : Const LOOM_INK_900_G = 17 : Const LOOM_INK_900_B = 10 // #16110a
Const LOOM_INK_700_R = 58 : Const LOOM_INK_700_G = 46 : Const LOOM_INK_700_B = 29 // #3a2e1d

// Semantic
Const LOOM_SUCCESS_R = 76 : Const LOOM_SUCCESS_G = 174 : Const LOOM_SUCCESS_B = 79
Const LOOM_WARNING_R = 230 : Const LOOM_WARNING_G = 162 : Const LOOM_WARNING_B = 58
Const LOOM_DANGER_R = 184 : Const LOOM_DANGER_G = 48 : Const LOOM_DANGER_B = 42


// -----------------------------------------------------------------------------
// State: fonts loaded by LoomTheme_Init. For the alpha skeleton we just use
// the Blitz default font; later phases will load MedievalSharp / Cinzel /
// Cormorant Garamond ttf files from Data/Loom/Fonts/ when those are added.
// -----------------------------------------------------------------------------
Global LoomFont_Body = 0
Global LoomFont_Display = 0


// =============================================================================
// LoomTheme_Init -- one-time setup. Safe to call multiple times.
// =============================================================================
Function LoomTheme_Init()
// Future: LoadFont "Data/Loom/Fonts/MedievalSharp.ttf", 48 etc.
// For the alpha, leave both as 0 -- Blitz uses the default system font.
End Function


// =============================================================================
// Drawing primitives
// =============================================================================

Function LoomColor(r, g, b)
Color r, g, b
End Function

Function LoomFill(x, y, w, h, r, g, b)
Color r, g, b
Rect x, y, w, h, True
End Function

Function LoomBorder(x, y, w, h, r, g, b)
Color r, g, b
Rect x, y, w, h, False
End Function

// Vertical gradient by drawing N horizontal stripes with interpolated color.
// Step size is 1 pixel for smoothness; if h is large and perf matters, callers
// can fake bigger steps. This is fine for splashes and panels.
Function LoomGradientV(x, y, w, h, r1, g1, b1, r2, g2, b2)
If h <= 0 Then Return
Local i = 0
Local rr, gg, bb
For i = 0 To h - 1
// Interpolate between (r1,g1,b1) at i=0 and (r2,g2,b2) at i=h-1.
// Multiply-then-divide to keep ints in range.
rr = r1 + ((r2 - r1) * i) / (h - 1)
gg = g1 + ((g2 - g1) * i) / (h - 1)
bb = b1 + ((b2 - b1) * i) / (h - 1)
Comment on lines +135 to +137
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle 1px gradients without dividing by zero

LoomGradientV divides by (h - 1) for color interpolation, which triggers an arithmetic exception when h = 1. A 1-pixel-tall fill is a valid input for this helper (e.g., thin bars or collapsed panels), so this can crash the editor as soon as such a call is added. Add a fast path for h = 1 (draw one line with either endpoint color) before entering the interpolation loop.

Useful? React with 👍 / 👎.

Color rr, gg, bb
Line x, y + i, x + w - 1, y + i
Next
End Function

Function LoomHRule(x, y, w, r, g, b)
Color r, g, b
Line x, y, x + w - 1, y
End Function

// align: 0 = left, 1 = center, 2 = right (relative to x).
Function LoomText(x, y, txt$, r, g, b, align = 0)
Color r, g, b
Text x, y, txt$, align, 0
End Function

// Convenience: text centered horizontally at cx. Vertically aligned by y top.
Function LoomTextCentered(cx, y, txt$, r, g, b)
Color r, g, b
Text cx, y, txt$, 1, 0
End Function
Loading
Loading