153 lines
4.2 KiB
Go
153 lines
4.2 KiB
Go
package inc
|
|
|
|
// TapeSlots — the two slots of Paul's Armilla.
|
|
//
|
|
// In canon Paul's Armilla has TWO slots where the standard has one: Dex sits
|
|
// permanently in the first, and tapes picked up during the game go into the
|
|
// second. The wireframe deck does not know about this at all — this is the one
|
|
// HUD element that comes purely from the wiki.
|
|
//
|
|
// Interaction follows the selected verb, like everything else:
|
|
//
|
|
// talk → converse with whoever occupies the slot
|
|
// look → describe the slot
|
|
// use + a selected tape → load it into slot 2
|
|
//
|
|
// Nothing here touches the world: this is the Vox side of the two channels.
|
|
|
|
import (
|
|
inkwell "git.teletypegames.org/engines/inkwell"
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
"github.com/hajimehoshi/ebiten/v2/vector"
|
|
)
|
|
|
|
type TapeSlots struct {
|
|
Name string
|
|
W *World
|
|
Bounds inkwell.Rectangle
|
|
}
|
|
|
|
func (t *TapeSlots) GetName() string { return t.Name }
|
|
|
|
func (t *TapeSlots) BlocksClickAt(p inkwell.Point) bool {
|
|
return t.W.HUDVisible() && t.Bounds.Contains(p)
|
|
}
|
|
|
|
// slotRects splits the widget bounds into the two slot rectangles.
|
|
func (t *TapeSlots) slotRects() (inkwell.Rectangle, inkwell.Rectangle) {
|
|
b := t.Bounds
|
|
w := (b.W - Pad) / 2
|
|
return inkwell.Rect(b.X, b.Y, w, b.H), inkwell.Rect(b.X+w+Pad, b.Y, w, b.H)
|
|
}
|
|
|
|
func (t *TapeSlots) Tick(ctx *inkwell.UICtx) {
|
|
if !t.W.HUDVisible() {
|
|
return
|
|
}
|
|
g := ctx.Game
|
|
mp := g.Input.Point()
|
|
r1, r2 := t.slotRects()
|
|
|
|
// Hover label, so the StatusLine spells out what is about to happen.
|
|
switch {
|
|
case r1.Contains(mp):
|
|
g.SetHoverLabel(TapeDisplayName(Dex))
|
|
case r2.Contains(mp):
|
|
if s := t.W.Slot2(); s != "" {
|
|
g.SetHoverLabel(itemLabel(g, s))
|
|
} else {
|
|
g.SetHoverLabel("empty tape slot")
|
|
}
|
|
}
|
|
|
|
if !g.Input.LeftClicked() {
|
|
return
|
|
}
|
|
if !r1.Contains(mp) && !r2.Contains(mp) {
|
|
return
|
|
}
|
|
g.Input.ConsumeLeft()
|
|
|
|
slot2 := r2.Contains(mp)
|
|
sel := g.Inventory.Selected()
|
|
|
|
// Loading a tape into slot 2.
|
|
if slot2 && sel != "" {
|
|
g.Inventory.Select("")
|
|
if IsTapeItem(sel) {
|
|
t.W.SetPending(sel)
|
|
t.W.Do(inkwell.RunScript(ScriptTapeInsert))
|
|
return
|
|
}
|
|
t.W.Do(TapeSay(Dex, "That isn't a tape, Paul. That's an object. There is a difference."))
|
|
return
|
|
}
|
|
if !slot2 && sel != "" {
|
|
g.Inventory.Select("")
|
|
t.W.Do(TapeSay(Dex, "The first slot is mine. I'm not moving."))
|
|
return
|
|
}
|
|
|
|
switch g.SelectedVerb() {
|
|
case "talk":
|
|
if !slot2 {
|
|
t.W.Do(Paused(t.W, inkwell.RunDialogue(DlgDex)))
|
|
return
|
|
}
|
|
if s := t.W.Slot2(); s != "" {
|
|
t.W.Do(Paused(t.W, inkwell.RunDialogue(TapeDialogue(s))))
|
|
return
|
|
}
|
|
t.W.Do(TapeSay(Dex, "Empty. Nobody to talk to."))
|
|
default: // look, take, use all fall back to a description
|
|
if !slot2 {
|
|
t.W.Do(TapeSay(Dex, "Me. Four kilobytes of a dead man. Be grateful."))
|
|
return
|
|
}
|
|
if s := t.W.Slot2(); s != "" {
|
|
t.W.Do(TapeSay(Dex, "That is the second slot. Be careful what you let in."))
|
|
return
|
|
}
|
|
t.W.Do(TapeSay(Dex, "The second slot is empty. That's the rarer state."))
|
|
}
|
|
}
|
|
|
|
func (t *TapeSlots) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
|
|
if !t.W.HUDVisible() {
|
|
return
|
|
}
|
|
g := ctx.Game
|
|
th := g.Theme()
|
|
r1, r2 := t.slotRects()
|
|
|
|
// Two text rows per slot: the slot number on top, its occupant below.
|
|
// Both rows are LineH tall, so nothing can overlap the border.
|
|
drawSlot := func(r inkwell.Rectangle, label, body string, filled bool) {
|
|
bg, border, col := th.InventorySlotBG, th.ChatLogSystem, th.ChatLogSystem
|
|
if filled {
|
|
bg, border, col = th.PanelBG, th.CharacterPanelBorder, th.ChatLogResponse
|
|
}
|
|
vector.DrawFilledRect(dst, float32(r.X), float32(r.Y), float32(r.W), float32(r.H), bg, false)
|
|
vector.StrokeRect(dst, float32(r.X), float32(r.Y), float32(r.W), float32(r.H), 1, border, false)
|
|
|
|
x, inner := int(r.X)+Pad, int(r.W)-Pad*2
|
|
drawTextC(dst, label, x, int(r.Y)+Pad/2, th.ChatLogSystem)
|
|
drawTextC(dst, clip(body, inner), x, int(r.Y)+Pad/2+LineH, col)
|
|
}
|
|
|
|
drawSlot(r1, "SLOT 1", TapeDisplayName(Dex), true)
|
|
if s := t.W.Slot2(); s != "" {
|
|
drawSlot(r2, "SLOT 2", itemLabel(g, s), true)
|
|
} else {
|
|
drawSlot(r2, "SLOT 2", "empty", false)
|
|
}
|
|
}
|
|
|
|
// itemLabel is a short display label for an inventory item.
|
|
func itemLabel(g *inkwell.Game, name string) string {
|
|
if it, ok := g.ItemManager.Get(name); ok && it.Description != "" {
|
|
return it.Description
|
|
}
|
|
return name
|
|
}
|