radial fix
This commit is contained in:
@@ -99,6 +99,17 @@ func (e *engine) handleSceneInput() {
|
|||||||
}
|
}
|
||||||
g.Input.ConsumeLeft()
|
g.Input.ConsumeLeft()
|
||||||
h := e.hotspotAt(mp)
|
h := e.hotspotAt(mp)
|
||||||
|
if h == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
g.invokeHotspotVerb(h, g.SelectedVerb())
|
||||||
|
}
|
||||||
|
|
||||||
|
// invokeHotspotVerb runs the action bound to verb on hotspot h, honoring
|
||||||
|
// the SCUMM use-with-item flow when an inventory item is selected.
|
||||||
|
// Shared by left-click hotspot resolution and the verb-coin widget so
|
||||||
|
// both paths log, flash and queue identically.
|
||||||
|
func (g *Game) invokeHotspotVerb(h *Hotspot, verb string) {
|
||||||
if h == nil {
|
if h == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -128,9 +139,9 @@ func (e *engine) handleSceneInput() {
|
|||||||
g.Inventory.Select("")
|
g.Inventory.Select("")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
a := h.handler(g.SelectedVerb())
|
a := h.handler(verb)
|
||||||
if a == nil {
|
if a == nil {
|
||||||
if v, ok := g.VerbManager.Get(g.SelectedVerb()); ok && v.Default != nil {
|
if v, ok := g.VerbManager.Get(verb); ok && v.Default != nil {
|
||||||
a = v.Default
|
a = v.Default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -138,8 +149,8 @@ func (e *engine) handleSceneInput() {
|
|||||||
g.FlashLine("Semmi említésre méltó.")
|
g.FlashLine("Semmi említésre méltó.")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
g.LogAction("> " + verbLabel(g, g.SelectedVerb()) + " " + targetLabel)
|
g.LogAction("> " + verbLabel(g, verb) + " " + targetLabel)
|
||||||
g.queueAction(a, "hotspot "+g.SelectedVerb()+" "+h.Name)
|
g.queueAction(a, "hotspot "+verb+" "+h.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func verbLabel(g *Game, name string) string {
|
func verbLabel(g *Game, name string) string {
|
||||||
@@ -149,20 +160,7 @@ func verbLabel(g *Game, name string) string {
|
|||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *engine) hotspotAt(p Point) *Hotspot {
|
func (e *engine) hotspotAt(p Point) *Hotspot { return e.g.HotspotAt(p) }
|
||||||
g := e.g
|
|
||||||
if g.currentScene == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
s := g.SceneManager.MustGet(g.currentScene)
|
|
||||||
for i := range s.Hotspots {
|
|
||||||
h := &s.Hotspots[i]
|
|
||||||
if h.Area != nil && h.Area.Contains(p) {
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *engine) Draw(screen *ebiten.Image) {
|
func (e *engine) Draw(screen *ebiten.Image) {
|
||||||
g := e.g
|
g := e.g
|
||||||
|
|||||||
19
core.game.go
19
core.game.go
@@ -185,6 +185,25 @@ func (g *Game) appendLog(m LogMessage) {
|
|||||||
// Messages returns the current log buffer (read-only).
|
// Messages returns the current log buffer (read-only).
|
||||||
func (g *Game) Messages() []LogMessage { return g.messages }
|
func (g *Game) Messages() []LogMessage { return g.messages }
|
||||||
|
|
||||||
|
// HotspotAt returns the topmost hotspot in the current scene whose Area
|
||||||
|
// contains p, or nil if none. Exposed so widgets (e.g., a verb coin
|
||||||
|
// that only opens over hotspots) can do their own hit-tests in their
|
||||||
|
// Tick — the engine's own resolution runs only after every widget had a
|
||||||
|
// chance, so widgets can't rely on it.
|
||||||
|
func (g *Game) HotspotAt(p Point) *Hotspot {
|
||||||
|
if g.currentScene == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s := g.SceneManager.MustGet(g.currentScene)
|
||||||
|
for i := range s.Hotspots {
|
||||||
|
h := &s.Hotspots[i]
|
||||||
|
if h.Area != nil && h.Area.Contains(p) {
|
||||||
|
return h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// CharacterInScene reports whether a registered character is listed as
|
// CharacterInScene reports whether a registered character is listed as
|
||||||
// an actor in the current scene. Used by the CharacterPanel widget so
|
// an actor in the current scene. Used by the CharacterPanel widget so
|
||||||
// it auto-hides when the character isn't in view.
|
// it auto-hides when the character isn't in view.
|
||||||
|
|||||||
@@ -25,6 +25,12 @@ type RadialVerbs struct {
|
|||||||
AlwaysVisible bool
|
AlwaysVisible bool
|
||||||
Center Point
|
Center Point
|
||||||
|
|
||||||
|
// HotspotOnly (trigger mode only) makes the wheel open only when the
|
||||||
|
// trigger lands on a registered hotspot in the current scene. Right-
|
||||||
|
// clicks on empty space fall through to handleSceneInput (so the
|
||||||
|
// SCUMM "reset verb to look" behaviour still works there).
|
||||||
|
HotspotOnly bool
|
||||||
|
|
||||||
// Labels overrides the rendered verb label per verb Name. Useful when
|
// Labels overrides the rendered verb label per verb Name. Useful when
|
||||||
// the regular Verb.Label is too long to fit inside the wheel — e.g.
|
// the regular Verb.Label is too long to fit inside the wheel — e.g.
|
||||||
// {"use": "Tedd", "take": "Vedd"}. Falls back to Verb.Label.
|
// {"use": "Tedd", "take": "Vedd"}. Falls back to Verb.Label.
|
||||||
@@ -33,6 +39,7 @@ type RadialVerbs struct {
|
|||||||
// runtime
|
// runtime
|
||||||
visible bool
|
visible bool
|
||||||
center Point
|
center Point
|
||||||
|
pendingHotspot *Hotspot // remembered hotspot when HotspotOnly opens the coin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RadialVerbs) GetName() string { return r.Name }
|
func (r *RadialVerbs) GetName() string { return r.Name }
|
||||||
@@ -64,6 +71,16 @@ func (r *RadialVerbs) Tick(ctx *UICtx) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
mp := g.Input.Point()
|
mp := g.Input.Point()
|
||||||
|
// HotspotOnly: only open when the trigger lands on a hotspot.
|
||||||
|
// Empty-space clicks fall through (so the scene's right-click
|
||||||
|
// "reset verb" behaviour still fires from handleSceneInput).
|
||||||
|
var pending *Hotspot
|
||||||
|
if r.HotspotOnly {
|
||||||
|
pending = g.HotspotAt(mp)
|
||||||
|
if pending == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
// Don't open over a widget that would also claim this click —
|
// Don't open over a widget that would also claim this click —
|
||||||
// e.g. an InventoryBar. Each widget can declare its dead-zone
|
// e.g. an InventoryBar. Each widget can declare its dead-zone
|
||||||
// via the optional clickBlocker interface.
|
// via the optional clickBlocker interface.
|
||||||
@@ -73,12 +90,14 @@ func (r *RadialVerbs) Tick(ctx *UICtx) {
|
|||||||
r.consumeTrigger(g)
|
r.consumeTrigger(g)
|
||||||
r.center = mp
|
r.center = mp
|
||||||
r.visible = true
|
r.visible = true
|
||||||
|
r.pendingHotspot = pending
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
// modal: swallow secondary clicks (close without picking)
|
// modal: swallow secondary clicks (close without picking)
|
||||||
if r.triggerPressed(g) {
|
if r.triggerPressed(g) {
|
||||||
r.consumeTrigger(g)
|
r.consumeTrigger(g)
|
||||||
r.visible = false
|
r.visible = false
|
||||||
|
r.pendingHotspot = nil
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !g.Input.LeftClicked() {
|
if !g.Input.LeftClicked() {
|
||||||
@@ -88,9 +107,18 @@ func (r *RadialVerbs) Tick(ctx *UICtx) {
|
|||||||
idx := r.hitTest(g, g.Input.Point())
|
idx := r.hitTest(g, g.Input.Point())
|
||||||
if idx >= 0 {
|
if idx >= 0 {
|
||||||
names := g.VerbManager.Names()
|
names := g.VerbManager.Names()
|
||||||
g.SetSelectedVerb(names[idx])
|
verb := names[idx]
|
||||||
|
g.SetSelectedVerb(verb)
|
||||||
|
// Verb-coin behaviour: when the coin was opened over a hotspot
|
||||||
|
// (HotspotOnly mode), picking a slice immediately fires that
|
||||||
|
// verb's action on that hotspot — one round-trip per command,
|
||||||
|
// instead of forcing a follow-up left-click on the same target.
|
||||||
|
if r.pendingHotspot != nil {
|
||||||
|
g.invokeHotspotVerb(r.pendingHotspot, verb)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
r.visible = false
|
r.visible = false
|
||||||
|
r.pendingHotspot = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RadialVerbs) triggerPressed(g *Game) bool {
|
func (r *RadialVerbs) triggerPressed(g *Game) bool {
|
||||||
@@ -117,6 +145,21 @@ func (r *RadialVerbs) blockedAt(ctx *UICtx, p Point) bool {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
w := ctx.Game.UIManager.MustGet(name)
|
w := ctx.Game.UIManager.MustGet(name)
|
||||||
|
// DialogBox advertises its bounds via clickBlocker even when no
|
||||||
|
// dialog is open, so the verb-coin would refuse to pop up over
|
||||||
|
// scenery that happens to sit under the dialog rect. Skip the
|
||||||
|
// dialog when it's actually inactive.
|
||||||
|
if _, isDialog := w.(*DialogBox); isDialog && !ctx.Game.dialogueActive() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// CharacterPanel auto-hides when its character isn't in the
|
||||||
|
// current scene; treat the hidden panel as transparent so the
|
||||||
|
// coin can open over the empty space it would occupy.
|
||||||
|
if cp, isCP := w.(*CharacterPanel); isCP {
|
||||||
|
if cp.Character == "" || !ctx.Game.CharacterInScene(cp.Character) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
if b, ok := w.(clickBlocker); ok && b.BlocksClickAt(p) {
|
if b, ok := w.(clickBlocker); ok && b.BlocksClickAt(p) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user