initial commit

This commit is contained in:
2026-05-25 18:09:51 +02:00
commit df7219677e
58 changed files with 3646 additions and 0 deletions
+97
View File
@@ -0,0 +1,97 @@
package pncdsl
import (
"image/color"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
)
// dialogBox is the active-conversation UI: it shows the current node's
// lines (one at a time, advanced on click) followed by the visible choices.
type dialogBox struct {
dialogue *Dialogue
node *DialogueNode
lineIdx int // -1 = lines done, showing choices
choiceHits []Rectangle
}
func newDialogBox(d *Dialogue, n *DialogueNode) *dialogBox {
return &dialogBox{dialogue: d, node: n, lineIdx: 0}
}
// resolveChoices returns the list of choices currently visible to the player.
func (db *dialogBox) resolveChoices(ctx *Ctx) []DialogueChoice {
out := make([]DialogueChoice, 0, len(db.node.Choices))
for _, c := range db.node.Choices {
if c.Show != nil && !c.Show.Eval(ctx) {
continue
}
if c.Once && ctx.Game.State.Talked(db.node.Name+":"+c.Text) > 0 {
continue
}
out = append(out, c)
}
return out
}
// handleClick advances the dialog state. Returns the picked choice (if any).
func (db *dialogBox) handleClick(ctx *Ctx, p Point) (picked *DialogueChoice) {
if db.lineIdx >= 0 && db.lineIdx < len(db.node.Lines) {
db.lineIdx++
if db.lineIdx >= len(db.node.Lines) {
db.lineIdx = -1
db.choiceHits = nil
}
return nil
}
for i, r := range db.choiceHits {
if r.Contains(p) {
ch := db.resolveChoices(ctx)[i]
return &ch
}
}
return nil
}
func (db *dialogBox) currentLine() (DialogueLine, bool) {
if db.lineIdx >= 0 && db.lineIdx < len(db.node.Lines) {
return db.node.Lines[db.lineIdx], true
}
return DialogueLine{}, false
}
func (db *dialogBox) draw(dst *ebiten.Image, g *Game) {
// box across the bottom 60 px
vector.DrawFilledRect(dst, 0, 140, 320, 60, color.RGBA{15, 15, 25, 240}, false)
vector.StrokeRect(dst, 0, 140, 320, 60, 1, color.RGBA{80, 80, 110, 255}, false)
if ln, ok := db.currentLine(); ok {
speakerCol := color.RGBA{220, 220, 120, 255}
drawText(dst, ln.Speaker+":", 6, 142, speakerCol)
lines := wrapText(ln.Text, 300)
for i, l := range lines {
drawText(dst, l, 6, 158+i*14, color.White)
}
return
}
// choices
ctx := g.makeCtx()
choices := db.resolveChoices(ctx)
db.choiceHits = db.choiceHits[:0]
for i, c := range choices {
y := 144 + i*14
r := Rect(6, float64(y), 308, 13)
db.choiceHits = append(db.choiceHits, r)
mx, my := g.input.Pos()
hover := r.Contains(Point{X: float64(mx), Y: float64(my)})
bg := color.RGBA{30, 30, 45, 255}
if hover {
bg = color.RGBA{70, 70, 100, 255}
}
vector.DrawFilledRect(dst, float32(r.X), float32(r.Y), float32(r.W), float32(r.H), bg, false)
drawText(dst, c.Text, int(r.X)+2, int(r.Y)-1, color.White)
_ = i
}
}