more refact

This commit is contained in:
2026-08-30 23:01:48 +02:00
parent 3890edc946
commit 28b1662195
38 changed files with 353 additions and 663 deletions
+6 -1
View File
@@ -11,6 +11,7 @@ var (
ItemManager = inkwell.NewManager[Item]()
SceneManager = inkwell.NewManager[Scene]()
ScriptManager = inkwell.NewManager[Script]()
TapeManager = inkwell.NewManager[Tape]()
ThemeManager = inkwell.NewManager[Theme]()
WidgetManager = inkwell.NewManager[Widget]()
)
@@ -26,17 +27,21 @@ func New(o Opts) *inkwell.Game {
start = SceneAlley
}
prepareScene()
fillSelectorPins()
g := inkwell.NewGame("Real World", ScreenW, ScreenH)
g.MaxLogLines = 64
g.WindowScale = 2
g.SceneRect = inkwell.Rect(0, TopBarH, ScreenW, HUDTop-TopBarH)
g.Player = Paul
g.Walkboxes = sceneFloor
g.ExitLook = func(e inkwell.Exit) inkwell.Action {
return inkwell.Say(Paul, "That way: "+e.Label+".")
}
g.ExitTake = func(inkwell.Exit) inkwell.Action {
return inkwell.Say(Paul, "It's a way out, not a thing.")
}
g.UseWithFail = useWithFail
g.AssetManager = BackgroundManager
g.CharacterManager = CharacterManager
+6
View File
@@ -1,8 +1,14 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
)
func init() {
CharacterManager.Register(Character{
Name: Dex,
Label: "DEX",
Voice: inkwell.VoiceLog,
SpeechColor: Amber,
})
}
+6
View File
@@ -1,8 +1,14 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
)
func init() {
CharacterManager.Register(Character{
Name: TapeMystery,
Label: "UNKNOWN TAPE",
Voice: inkwell.VoiceLog,
SpeechColor: RGB(0xB9A98A),
})
}
+1 -1
View File
@@ -11,7 +11,7 @@ func init() {
W: 28,
H: 68,
Start: inkwell.Point{
X: 120,
X: 320,
Y: 232,
},
SpeechColor: Ink,
+6
View File
@@ -1,8 +1,14 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
)
func init() {
CharacterManager.Register(Character{
Name: TapeSupport,
Label: "TECH SUPPORT",
Voice: inkwell.VoiceLog,
SpeechColor: RGB(0xE8C86A),
})
}
+2 -2
View File
@@ -10,12 +10,12 @@ func init() {
Description: "black-market Armilla",
OnUseSelf: inkwell.Seq(
inkwell.Say(Paul, "Jailbroken. Pushes ads, but it was cheap."),
TapeSay(Dex, "Two slots, and both of them lie about the temperature. Don't trade me in for it."),
inkwell.Say(Dex, "Two slots, and both of them lie about the temperature. Don't trade me in for it."),
),
OnUseWith: map[string]inkwell.Action{
ItemNoodleLetter: inkwell.Seq(
inkwell.Say(Paul, "A letter and a bracelet. Brilliant."),
TapeSay(Dex, "Nothing. Which is what I'd charge for it."),
inkwell.Say(Dex, "Nothing. Which is what I'd charge for it."),
),
},
})
+1 -1
View File
@@ -10,7 +10,7 @@ func init() {
Description: "unmarked Personal Tape",
OnUseSelf: inkwell.Seq(
inkwell.Say(Paul, "Password-locked. Of course."),
TapeSay(Dex, "Put it in the second slot if you care that much. I did warn you."),
inkwell.Say(Dex, "Put it in the second slot if you care that much. I did warn you."),
),
})
}
+1 -1
View File
@@ -10,7 +10,7 @@ func init() {
Description: "Noodle's letter",
OnUseSelf: inkwell.Seq(
inkwell.Say(Paul, "“Come out, Paul. Not a phone conversation.” That's all it says."),
TapeSay(Dex, "And now you're here. And he isn't."),
inkwell.Say(Dex, "And now you're here. And he isn't."),
),
})
}
+4 -29
View File
@@ -34,6 +34,10 @@ const (
VarSlot2 = "armilla.slot2"
VarArmillaStrip = "armilla.strip"
VarDecay = "decay_level"
VarMode = "mode"
VarNote = "note"
NotePaused = "paused"
)
const (
@@ -89,32 +93,3 @@ const (
BgShowroom = "bg_showroom"
BgColumbarium = "bg_columbarium"
)
var Tapes = map[string]string{
Dex: "DEX",
TapeMystery: "UNKNOWN TAPE",
TapeSupport: "TECH SUPPORT",
}
func IsTape(name string) bool { _, ok := Tapes[name]; return ok }
func TapeDisplayName(name string) string {
if d, ok := Tapes[name]; ok {
return d
}
return name
}
var TapeItems = map[string]string{
ItemMysteryTape: TapeMystery,
}
func IsTapeItem(item string) bool { _, ok := TapeItems[item]; return ok }
func TapeDialogue(item string) string {
switch item {
case ItemMysteryTape:
return DlgMysteryTape
}
return DlgDex
}
+4 -4
View File
@@ -47,7 +47,7 @@ func hidingPlace() inkwell.Hotspot {
inkwell.Give(ItemMysteryTape),
inkwell.SetFlag(FlagHasTape),
inkwell.Say(Paul, "A Personal Tape. No label, no seal."),
TapeSay(Dex, "Password-locked. And somebody very much did not want it found."),
inkwell.Say(Dex, "Password-locked. And somebody very much did not want it found."),
),
),
inkwell.Say(Paul, "One loose brick. I'm not taking the alley apart over it."),
@@ -64,13 +64,13 @@ func backDoor() inkwell.Hotspot {
OnLook: inkwell.Say(Paul, "Keypad. Four digits, worn keys."),
OnUse: inkwell.Seq(
inkwell.Say(Paul, "Locked. And I'm not guessing four digits."),
TapeSay(Dex, "The wear is heaviest on the two and the seven. That isn't a code. It's fewer options."),
inkwell.Say(Dex, "The wear is heaviest on the two and the seven. That isn't a code. It's fewer options."),
),
OnTalk: inkwell.Say(Paul, "To the door? I'm not there yet."),
OnUseWith: map[string]inkwell.Action{
ItemBlackArmilla: inkwell.Seq(
inkwell.Say(Paul, "A black-market bracelet doesn't open a keypad."),
TapeSay(Dex, "But you do get an advert out of it."),
inkwell.Say(Dex, "But you do get an advert out of it."),
),
},
}
@@ -84,7 +84,7 @@ func bin() inkwell.Hotspot {
OnLook: inkwell.Say(Paul, "Somebody's been through it. Thoroughly."),
OnUse: inkwell.Seq(
inkwell.Say(Paul, "Already turned out. I'm not going to be the second one."),
TapeSay(Dex, "The police don't go through bins. So who did?"),
inkwell.Say(Dex, "The police don't go through bins. So who did?"),
),
}
}
-25
View File
@@ -6,13 +6,6 @@ import (
type Scene = inkwell.Scene
func prepareScene() {
fillSelectorPins()
for _, entity := range SceneManager.All() {
SceneManager.Set(sceneDefaults(entity))
}
}
var sceneFloor = []inkwell.Polygon{
inkwell.Poly(
inkwell.Point{
@@ -32,24 +25,6 @@ var sceneFloor = []inkwell.Polygon{
),
}
func sceneDefaults(s Scene) Scene {
if s.Actors == nil {
s.Actors = []inkwell.SceneActor{
{
CharacterName: Paul,
At: inkwell.Point{
X: 320,
Y: 232,
},
},
}
}
if s.Walkboxes == nil {
s.Walkboxes = sceneFloor
}
return s
}
func setVarIfEmpty(name string, v any) inkwell.Action {
return Fn(func(ctx *inkwell.Ctx) {
if ctx.Game.State.Var(name) == nil {
+1 -1
View File
@@ -15,7 +15,7 @@ func init() {
UseTheme(NokiaPunk),
inkwell.Say("norman", "No — this isn't what you were supposed to do!"),
inkwell.Wait(0.6),
TapeSay(TapeMystery, "Thank you, Paul. You did exactly what I asked, the whole way through."),
inkwell.Say(TapeMystery, "Thank you, Paul. You did exactly what I asked, the whole way through."),
inkwell.Wait(1.0),
inkwell.ShowEnd("REAL WORLD — end of game two"),
),
+1 -1
View File
@@ -10,7 +10,7 @@ func init() {
Actions: inkwell.Seq(
inkwell.SetFlag(FlagPoliceTip),
inkwell.Say(Paul, "Back alley. Just like the clerk said."),
TapeSay(Dex, "A government office tipped you off to remove something from a scene. That doesn't strike you as odd?"),
inkwell.Say(Dex, "A government office tipped you off to remove something from a scene. That doesn't strike you as odd?"),
),
})
}
+2 -2
View File
@@ -18,8 +18,8 @@ func init() {
ctx.Game.State.SetVar(VarArmillaStrip, "SLOT2: READING")
}),
inkwell.Say(Paul, "Right. Let's see who you are."),
TapeSay(Dex, "Clicked in. Spinning. And now: nothing."),
TapeSay(Dex, "A stranger's tape in your own Armilla, Paul. I hope you know what you're doing."),
inkwell.Say(Dex, "Clicked in. Spinning. And now: nothing."),
inkwell.Say(Dex, "A stranger's tape in your own Armilla, Paul. I hope you know what you're doing."),
),
})
}
+59
View File
@@ -0,0 +1,59 @@
package inc
import (
"math/rand"
inkwell "git.teletypegames.org/engines/inkwell"
)
func useWithFail(item string, h *inkwell.Hotspot) inkwell.Action {
key := "fail." + item + "." + h.Name
World.G.State.NoteTalked(key)
n := World.G.State.Talked(key)
return inkwell.Seq(
inkwell.Say(Paul, pick(paulFails, n)),
inkwell.Say(Dex, dexFail(n)),
)
}
var paulFails = []string{
"No. That's not going to work.",
"Tried that. It didn't improve.",
"All right, I know that one doesn't work.",
"Now it's personal.",
}
var dexDry = []string{
"No. Not like that.",
"I heard it. Nothing happened.",
}
var dexTease = []string{
"Twice the same. The second one rarely goes better.",
"Do it a few more times, maybe physics reconsiders.",
}
func dexFail(n int) string {
switch {
case n <= 1:
return pick(dexDry, n)
case n == 2:
return pick(dexTease, n)
default:
return "Paul. Leave it. Look again at what you're carrying — that's where it is."
}
}
func pick(s []string, n int) string {
switch {
case len(s) == 0:
return ""
case n <= 0:
return s[0]
case n-1 < len(s):
return s[n-1]
default:
return s[rand.Intn(len(s))]
}
}
+8
View File
@@ -0,0 +1,8 @@
package inc
func init() {
TapeManager.Register(Tape{
Name: Dex,
Dialogue: DlgDex,
})
}
+32
View File
@@ -0,0 +1,32 @@
package inc
type Tape struct {
Name string
Item string
Dialogue string
}
func (t Tape) GetName() string { return t.Name }
func IsTape(name string) bool { return TapeManager.Has(name) }
func TapeOf(item string) (Tape, bool) {
for _, t := range TapeManager.All() {
if t.Item != "" && t.Item == item {
return t, true
}
}
return Tape{}, false
}
func IsTapeItem(item string) bool {
_, ok := TapeOf(item)
return ok
}
func TapeDialogue(item string) string {
if t, ok := TapeOf(item); ok && t.Dialogue != "" {
return t.Dialogue
}
return DlgDex
}
+9
View File
@@ -0,0 +1,9 @@
package inc
func init() {
TapeManager.Register(Tape{
Name: TapeMystery,
Item: ItemMysteryTape,
Dialogue: DlgMysteryTape,
})
}
+7
View File
@@ -0,0 +1,7 @@
package inc
func init() {
TapeManager.Register(Tape{
Name: TapeSupport,
})
}
-107
View File
@@ -1,107 +0,0 @@
package inc
import (
"image"
"image/color"
"unicode/utf8"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/ebitenutil"
)
const (
glyphW = 6
glyphH = 16
)
var scratch *ebiten.Image
func textW(s string) int { return utf8.RuneCountInString(s) * glyphW }
func drawTextC(dst *ebiten.Image, s string, x, y int, c color.Color) {
if s == "" {
return
}
w, h := textW(s)+glyphW, glyphH
if scratch == nil || scratch.Bounds().Dx() < w || scratch.Bounds().Dy() < h {
nw, nh := w, h
if nw < 320 {
nw = 320
}
scratch = ebiten.NewImage(nw, nh)
}
scratch.Clear()
ebitenutil.DebugPrintAt(scratch, s, 0, 0)
if c == nil {
c = color.White
}
r, g, b, a := c.RGBA()
op := &ebiten.DrawImageOptions{}
op.GeoM.Translate(float64(x), float64(y))
op.ColorScale.Scale(
float32(r)/0xffff, float32(g)/0xffff, float32(b)/0xffff, float32(a)/0xffff,
)
dst.DrawImage(scratch.SubImage(image.Rect(0, 0, w, h)).(*ebiten.Image), op)
}
func wrap(s string, maxPx int) []string {
if maxPx <= glyphW || textW(s) <= maxPx {
return []string{s}
}
var out []string
line, word := "", ""
flush := func() {
if line != "" {
out = append(out, line)
line = ""
}
}
emit := func() {
if word == "" {
return
}
switch {
case line == "":
line = word
case textW(line+" "+word) <= maxPx:
line += " " + word
default:
flush()
line = word
}
word = ""
}
for _, r := range s {
switch r {
case ' ':
emit()
case '\n':
emit()
flush()
default:
word += string(r)
}
}
emit()
flush()
if len(out) == 0 {
return []string{s}
}
return out
}
func clip(s string, maxPx int) string {
r := []rune(s)
max := maxPx / glyphW
switch {
case max <= 0:
return ""
case len(r) <= max:
return s
case max == 1:
return string(r[:1])
default:
return string(r[:max-1]) + "…"
}
}
-21
View File
@@ -1,21 +0,0 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
"github.com/hajimehoshi/ebiten/v2"
)
func init() {
WidgetManager.Register(&actionPump{
Name: "pump",
})
}
type actionPump struct {
Name string
}
func (p *actionPump) GetName() string { return p.Name }
func (p *actionPump) Layer() inkwell.Layer { return inkwell.LayerScene }
func (p *actionPump) Tick(ctx *inkwell.UICtx) { World.PumpTick(ctx.DT) }
func (p *actionPump) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {}
+5 -9
View File
@@ -16,14 +16,12 @@ type hudFrame struct {
Name string
}
func (h *hudFrame) GetName() string { return h.Name }
func (h *hudFrame) Layer() inkwell.Layer { return inkwell.LayerPanel }
func (h *hudFrame) Tick(ctx *inkwell.UICtx) {}
func (h *hudFrame) GetName() string { return h.Name }
func (h *hudFrame) VisibleWhen() inkwell.Condition { return whenPlaying }
func (h *hudFrame) Layer() inkwell.Layer { return inkwell.LayerPanel }
func (h *hudFrame) Tick(ctx *inkwell.UICtx) {}
func (h *hudFrame) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
if !World.HUDVisible() {
return
}
th := ctx.Game.Theme()
vector.DrawFilledRect(dst, 0, HUDTop+1, ScreenW, ScreenH-HUDTop-1, th.PanelBG, false)
@@ -32,6 +30,4 @@ func (h *hudFrame) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
vector.StrokeLine(dst, DividerX, HUDTop+1, DividerX, ScreenH, 1, th.CharacterPanelBorder, false)
}
func (h *hudFrame) BlocksClickAt(p inkwell.Point) bool {
return World.HUDVisible() && p.Y >= HUDTop
}
func (h *hudFrame) BlocksClickAt(p inkwell.Point) bool { return p.Y >= HUDTop }
+3 -2
View File
@@ -5,8 +5,9 @@ import (
)
func init() {
WidgetManager.Register(gated("inventory", &inkwell.InventoryBar{
WidgetManager.Register(&inkwell.InventoryBar{
Name: "inventory",
When: whenPlaying,
Origin: inkwell.Point{
X: Pad + 2,
Y: invY,
@@ -15,5 +16,5 @@ func init() {
Cols: 4,
SlotSize: invSlot,
Gap: invGap,
}))
})
}
+6 -9
View File
@@ -19,23 +19,20 @@ type letterbox struct {
Bar float64
}
func (l *letterbox) GetName() string { return l.Name }
func (l *letterbox) Layer() inkwell.Layer { return inkwell.LayerScene }
func (l *letterbox) GetName() string { return l.Name }
func (l *letterbox) VisibleWhen() inkwell.Condition { return whenCutscene }
func (l *letterbox) Layer() inkwell.Layer { return inkwell.LayerScene }
func (l *letterbox) Tick(ctx *inkwell.UICtx) {
if World.Mode() != ModeCutscene || !World.TapeWaiting() {
if !World.TapeWaiting() {
return
}
if inpututil.IsKeyJustPressed(ebiten.KeySpace) {
World.TakeTapeOffer()
}
}
func (l *letterbox) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
if World.Mode() != ModeCutscene {
return
}
g := ctx.Game
th := g.Theme()
bar := l.Bar
@@ -48,7 +45,7 @@ func (l *letterbox) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
vector.DrawFilledRect(dst, 0, h-float32(bar), w, float32(bar), black, false)
if World.TapeWaiting() {
msg := "SPACE — " + TapeDisplayName(Dex) + " has something to say"
drawTextC(dst, msg, g.Width-textW(msg)-4, g.Height-int(bar)+4, th.ChatLogResponse)
msg := "SPACE — " + g.CharacterLabel(Dex) + " has something to say"
g.DrawText(dst, msg, g.Width-inkwell.TextWidth(msg)-4, g.Height-int(bar)+4, th.ChatLogResponse)
}
}
+6 -37
View File
@@ -2,7 +2,6 @@ package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
"github.com/hajimehoshi/ebiten/v2"
)
type Widget = inkwell.Widget
@@ -11,7 +10,7 @@ const (
ScreenW = 640
ScreenH = 380
LineH = glyphH + 2
LineH = inkwell.GlyphH + 2
Pad = 6
TopBarH = LineH + 2
@@ -28,38 +27,8 @@ const (
invGap = 4
)
type gate struct {
Name string
Inner Widget
}
func gated(name string, inner Widget) Widget {
return &gate{
Name: name,
Inner: inner,
}
}
func (n *gate) GetName() string { return n.Name }
func (n *gate) Layer() inkwell.Layer { return inkwell.LayerOf(n.Inner) }
func (n *gate) Tick(ctx *inkwell.UICtx) {
if World.HUDVisible() {
n.Inner.Tick(ctx)
}
}
func (n *gate) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
if World.HUDVisible() {
n.Inner.Draw(dst, ctx)
}
}
func (n *gate) BlocksClickAt(p inkwell.Point) bool {
if !World.HUDVisible() {
return false
}
b, ok := n.Inner.(interface{ BlocksClickAt(inkwell.Point) bool })
return ok && b.BlocksClickAt(p)
}
var (
whenPlaying = inkwell.VarEq(VarMode, string(ModePlay))
whenCutscene = inkwell.VarEq(VarMode, string(ModeCutscene))
whenUnpaused = inkwell.And(whenPlaying, inkwell.Not(inkwell.VarEq(VarNote, NotePaused)))
)
+2 -43
View File
@@ -2,52 +2,11 @@ package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/inpututil"
)
func init() {
WidgetManager.Register(&sceneNav{
WidgetManager.Register(&inkwell.SceneNav{
Name: "scene_nav",
When: whenUnpaused,
})
}
type sceneNav struct {
Name string
}
func (n *sceneNav) GetName() string { return n.Name }
func (n *sceneNav) Layer() inkwell.Layer { return inkwell.LayerScene }
func (n *sceneNav) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {}
func (n *sceneNav) Tick(ctx *inkwell.UICtx) {
if !World.HUDVisible() || World.Paused() {
return
}
step := 0
switch {
case inpututil.IsKeyJustPressed(ebiten.KeyArrowRight):
step = 1
case inpututil.IsKeyJustPressed(ebiten.KeyArrowLeft):
step = -1
default:
return
}
if next := n.neighbour(ctx.Game, step); next != "" {
World.Do(inkwell.GoTo(next))
}
}
func (n *sceneNav) neighbour(g *inkwell.Game, step int) string {
deck := g.SceneManager.Names()
if len(deck) < 2 {
return ""
}
for i, name := range deck {
if name == World.G.CurrentScene() {
return deck[((i+step)%len(deck)+len(deck))%len(deck)]
}
}
return deck[0]
}
+3 -2
View File
@@ -5,10 +5,11 @@ import (
)
func init() {
WidgetManager.Register(gated("status", &inkwell.StatusLine{
WidgetManager.Register(&inkwell.StatusLine{
Name: "status",
When: whenPlaying,
Y: statusY,
Align: inkwell.AlignLeft,
ScreenWidth: DividerX,
}))
})
}
+9 -13
View File
@@ -20,19 +20,15 @@ type tapeChannel struct {
Bounds inkwell.Rectangle
}
func (t *tapeChannel) GetName() string { return t.Name }
func (t *tapeChannel) Layer() inkwell.Layer { return inkwell.LayerHUD }
func (t *tapeChannel) GetName() string { return t.Name }
func (t *tapeChannel) VisibleWhen() inkwell.Condition { return whenPlaying }
func (t *tapeChannel) Layer() inkwell.Layer { return inkwell.LayerHUD }
func (t *tapeChannel) Tick(ctx *inkwell.UICtx) {}
func (t *tapeChannel) BlocksClickAt(p inkwell.Point) bool {
return World.HUDVisible() && t.Bounds.Contains(p)
}
func (t *tapeChannel) BlocksClickAt(p inkwell.Point) bool { return t.Bounds.Contains(p) }
func (t *tapeChannel) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
if !World.HUDVisible() {
return
}
g := ctx.Game
th := g.Theme()
b := t.Bounds
@@ -44,7 +40,7 @@ func (t *tapeChannel) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
vector.DrawFilledRect(dst, float32(b.X), float32(b.Y), float32(b.W), float32(b.H), th.ChatLogBG, false)
speaker, speakerCol := t.lastSpeaker(g, th)
drawTextC(dst, speaker, int(b.X)+pad, int(b.Y)+pad, speakerCol)
g.DrawText(dst, speaker, int(b.X)+pad, int(b.Y)+pad, speakerCol)
hy := float32(b.Y+pad+lh) + 2
vector.StrokeLine(dst, float32(b.X)+pad, hy,
float32(b.X+b.W)-pad, hy, 1, th.CharacterPanelBorder, false)
@@ -70,7 +66,7 @@ func (t *tapeChannel) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
default:
col, txt = th.ChatLogSystem, m.Text
}
for _, wl := range wrap(txt, wrapW) {
for _, wl := range inkwell.WrapText(txt, wrapW) {
rendered = append(rendered, line{
text: wl,
col: col,
@@ -87,7 +83,7 @@ func (t *tapeChannel) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
rendered = rendered[start:]
}
for i, ln := range rendered {
drawTextC(dst, ln.text, int(b.X)+pad, top+i*lh, ln.col)
g.DrawText(dst, ln.text, int(b.X)+pad, top+i*lh, ln.col)
}
}
@@ -96,10 +92,10 @@ func (t *tapeChannel) lastSpeaker(g *inkwell.Game, th inkwell.Theme) (string, co
for i := len(msgs) - 1; i >= 0; i-- {
m := msgs[i]
if m.Kind == inkwell.LogResponse && IsTape(m.Speaker) {
return TapeDisplayName(m.Speaker), speechColor(g, m.Speaker, th.ChatLogResponse)
return g.CharacterLabel(m.Speaker), speechColor(g, m.Speaker, th.ChatLogResponse)
}
}
return TapeDisplayName(Dex), speechColor(g, Dex, th.ChatLogResponse)
return g.CharacterLabel(Dex), speechColor(g, Dex, th.ChatLogResponse)
}
func speechColor(g *inkwell.Game, name string, fallback color.Color) color.Color {
+14 -21
View File
@@ -18,12 +18,11 @@ type tapeSlots struct {
Bounds inkwell.Rectangle
}
func (t *tapeSlots) GetName() string { return t.Name }
func (t *tapeSlots) Layer() inkwell.Layer { return inkwell.LayerHUD }
func (t *tapeSlots) GetName() string { return t.Name }
func (t *tapeSlots) VisibleWhen() inkwell.Condition { return whenPlaying }
func (t *tapeSlots) Layer() inkwell.Layer { return inkwell.LayerHUD }
func (t *tapeSlots) BlocksClickAt(p inkwell.Point) bool {
return World.HUDVisible() && t.Bounds.Contains(p)
}
func (t *tapeSlots) BlocksClickAt(p inkwell.Point) bool { return t.Bounds.Contains(p) }
func (t *tapeSlots) slotRects() (inkwell.Rectangle, inkwell.Rectangle) {
b := t.Bounds
@@ -32,16 +31,13 @@ func (t *tapeSlots) slotRects() (inkwell.Rectangle, inkwell.Rectangle) {
}
func (t *tapeSlots) Tick(ctx *inkwell.UICtx) {
if !World.HUDVisible() {
return
}
g := ctx.Game
mp := g.Input.Point()
r1, r2 := t.slotRects()
switch {
case r1.Contains(mp):
g.SetHoverLabel(TapeDisplayName(Dex))
g.SetHoverLabel(g.CharacterLabel(Dex))
case r2.Contains(mp):
if s := World.Slot2(); s != "" {
g.SetHoverLabel(itemLabel(g, s))
@@ -68,12 +64,12 @@ func (t *tapeSlots) Tick(ctx *inkwell.UICtx) {
World.Do(inkwell.RunScript(ScriptTapeInsert))
return
}
World.Do(TapeSay(Dex, "That isn't a tape, Paul. That's an object. There is a difference."))
World.Do(inkwell.Say(Dex, "That isn't a tape, Paul. That's an object. There is a difference."))
return
}
if !slot2 && sel != "" {
g.Inventory.Select("")
World.Do(TapeSay(Dex, "The first slot is mine. I'm not moving."))
World.Do(inkwell.Say(Dex, "The first slot is mine. I'm not moving."))
return
}
@@ -87,24 +83,21 @@ func (t *tapeSlots) Tick(ctx *inkwell.UICtx) {
World.Do(Paused(inkwell.RunDialogue(TapeDialogue(s))))
return
}
World.Do(TapeSay(Dex, "Empty. Nobody to talk to."))
World.Do(inkwell.Say(Dex, "Empty. Nobody to talk to."))
default:
if !slot2 {
World.Do(TapeSay(Dex, "Me. Four kilobytes of a dead man. Be grateful."))
World.Do(inkwell.Say(Dex, "Me. Four kilobytes of a dead man. Be grateful."))
return
}
if s := World.Slot2(); s != "" {
World.Do(TapeSay(Dex, "That is the second slot. Be careful what you let in."))
World.Do(inkwell.Say(Dex, "That is the second slot. Be careful what you let in."))
return
}
World.Do(TapeSay(Dex, "The second slot is empty. That's the rarer state."))
World.Do(inkwell.Say(Dex, "The second slot is empty. That's the rarer state."))
}
}
func (t *tapeSlots) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
if !World.HUDVisible() {
return
}
g := ctx.Game
th := g.Theme()
r1, r2 := t.slotRects()
@@ -118,11 +111,11 @@ func (t *tapeSlots) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {
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)
g.DrawText(dst, label, x, int(r.Y)+Pad/2, th.ChatLogSystem)
g.DrawText(dst, inkwell.ClipText(body, inner), x, int(r.Y)+Pad/2+LineH, col)
}
drawSlot(r1, "SLOT 1", TapeDisplayName(Dex), true)
drawSlot(r1, "SLOT 1", g.CharacterLabel(Dex), true)
if s := World.Slot2(); s != "" {
drawSlot(r2, "SLOT 2", itemLabel(g, s), true)
} else {
+7 -19
View File
@@ -5,23 +5,11 @@ import (
)
func init() {
WidgetManager.Register(gated("topbar", &titleBar{
TopBar: &inkwell.TopBar{
Name: "topbar",
Height: TopBarH,
TimeVar: VarArmillaStrip,
},
}))
}
type titleBar struct {
*inkwell.TopBar
}
func (t *titleBar) Tick(ctx *inkwell.UICtx) {
t.LeftText = World.SceneTitle()
if World.Paused() {
t.LeftText += " — paused"
}
t.TopBar.Tick(ctx)
WidgetManager.Register(&inkwell.TopBar{
Name: "topbar",
When: whenPlaying,
Height: TopBarH,
TimeVar: VarArmillaStrip,
NoteVar: VarNote,
})
}
-110
View File
@@ -1,110 +0,0 @@
package inc
import (
"math/rand"
inkwell "git.teletypegames.org/engines/inkwell"
"github.com/hajimehoshi/ebiten/v2"
)
func init() {
WidgetManager.Register(&useWithGuard{
Name: "usewith_guard",
})
}
type useWithGuard struct {
Name string
}
func (u *useWithGuard) GetName() string { return u.Name }
func (u *useWithGuard) Layer() inkwell.Layer { return inkwell.LayerScene }
func (u *useWithGuard) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {}
func (u *useWithGuard) Tick(ctx *inkwell.UICtx) {
g := ctx.Game
if !World.HUDVisible() || !g.Input.LeftClicked() {
return
}
sel := g.Inventory.Selected()
if sel == "" {
return
}
h := g.HotspotAt(g.Input.Point())
if h == nil || hasAuthoredPair(g, h, sel) {
return
}
g.Input.ConsumeLeft()
g.Inventory.Select("")
label := h.Label
if label == "" {
label = h.Name
}
g.LogAction("> Use " + itemLabel(g, sel) + " on: " + label)
key := "fail." + sel + "." + h.Name
g.State.NoteTalked(key)
n := g.State.Talked(key)
World.Do(inkwell.Seq(
inkwell.Say(Paul, pick(paulFails, n)),
TapeSay(Dex, dexFail(n)),
))
}
func hasAuthoredPair(g *inkwell.Game, h *inkwell.Hotspot, item string) bool {
if h.OnUseWith != nil {
if a, ok := h.OnUseWith[item]; ok && a != nil {
return true
}
}
if it, ok := g.ItemManager.Get(item); ok && it.OnUseWith != nil {
if a, ok := it.OnUseWith[h.Name]; ok && a != nil {
return true
}
}
return false
}
var paulFails = []string{
"No. That's not going to work.",
"Tried that. It didn't improve.",
"All right, I know that one doesn't work.",
"Now it's personal.",
}
var dexDry = []string{
"No. Not like that.",
"I heard it. Nothing happened.",
}
var dexTease = []string{
"Twice the same. The second one rarely goes better.",
"Do it a few more times, maybe physics reconsiders.",
}
func dexFail(n int) string {
switch {
case n <= 1:
return pick(dexDry, n)
case n == 2:
return pick(dexTease, n)
default:
return "Paul. Leave it. Look again at what you're carrying — that's where it is."
}
}
func pick(s []string, n int) string {
switch {
case len(s) == 0:
return ""
case n <= 0:
return s[0]
case n-1 < len(s):
return s[n-1]
default:
return s[rand.Intn(len(s))]
}
}
-35
View File
@@ -1,35 +0,0 @@
package inc
import (
inkwell "git.teletypegames.org/engines/inkwell"
"github.com/hajimehoshi/ebiten/v2"
)
func init() {
WidgetManager.Register(&windowSizer{
Name: "window",
Scale: 2,
})
}
type windowSizer struct {
Name string
Scale int
done bool
}
func (s *windowSizer) GetName() string { return s.Name }
func (s *windowSizer) Layer() inkwell.Layer { return inkwell.LayerScene }
func (s *windowSizer) Draw(dst *ebiten.Image, ctx *inkwell.UICtx) {}
func (s *windowSizer) Tick(ctx *inkwell.UICtx) {
if s.done {
return
}
s.done = true
scale := s.Scale
if scale <= 0 {
scale = 2
}
ebiten.SetWindowSize(ctx.Game.Width*scale, ctx.Game.Height*scale)
}
+3 -74
View File
@@ -4,42 +4,7 @@ import (
inkwell "git.teletypegames.org/engines/inkwell"
)
func (w *world) Do(a inkwell.Action) {
if a != nil {
w.queue = append(w.queue, a)
}
}
func (w *world) PumpTick(dt float64) {
if w.running == nil {
if len(w.queue) == 0 {
return
}
w.running = w.queue[0].Start()
w.queue = w.queue[1:]
}
ctx := &inkwell.Ctx{
Game: w.G,
DT: dt,
}
if s, ok := w.G.SceneManager.Get(w.G.CurrentScene()); ok {
ctx.Scene = &s
}
if w.running.Tick(ctx) != inkwell.StatusRunning {
w.running = nil
}
}
func (w *world) SceneTitle() string {
s, ok := w.G.SceneManager.Get(w.G.CurrentScene())
if !ok {
return ""
}
if s.Title != "" {
return s.Title
}
return s.Name
}
func (w *world) Do(a inkwell.Action) { w.G.Do(a) }
type fnAction struct{ fn func(*inkwell.Ctx) }
@@ -70,42 +35,6 @@ func SetMode(m Mode) inkwell.Action {
return Fn(func(*inkwell.Ctx) { World.SetMode(m) })
}
func TapeSay(speaker, text string) inkwell.Action {
return &tapeSayAction{
speaker: speaker,
text: text,
}
}
type tapeSayAction struct{ speaker, text string }
func (a *tapeSayAction) Start() inkwell.Runner {
return &tapeSayRunner{
spec: a,
}
}
type tapeSayRunner struct {
spec *tapeSayAction
started bool
elapsed float64
duration float64
}
func (r *tapeSayRunner) Tick(ctx *inkwell.Ctx) inkwell.Status {
if !r.started {
r.started = true
r.duration = 1.2 + float64(len([]rune(r.spec.text)))*0.05
ctx.Game.LogResponse(r.spec.speaker, r.spec.text)
}
r.elapsed += ctx.DT
if r.elapsed >= r.duration {
return inkwell.StatusDone
}
return inkwell.StatusRunning
}
func TapeOffer(line inkwell.Action) inkwell.Action {
return Fn(func(*inkwell.Ctx) {
World.tapeLine = line
@@ -149,11 +78,11 @@ type pausedRunner struct {
func (r *pausedRunner) Tick(ctx *inkwell.Ctx) inkwell.Status {
if !r.started {
r.started = true
World.paused = true
World.SetNote(NotePaused)
}
s := r.inner.Tick(ctx)
if s != inkwell.StatusRunning {
World.paused = false
World.SetNote("")
}
return s
}
+11 -13
View File
@@ -15,12 +15,7 @@ const (
type world struct {
G *inkwell.Game
mode Mode
paused bool
pending string
queue []inkwell.Action
running inkwell.Runner
tapeWaiting bool
tapeLine inkwell.Action
@@ -30,22 +25,25 @@ var World = &world{}
func (w *world) attach(g *inkwell.Game) {
w.G = g
w.mode = ModePlay
w.paused = false
w.pending = ""
w.queue = nil
w.running = nil
w.tapeWaiting = false
w.tapeLine = nil
w.SetMode(ModePlay)
w.SetNote("")
}
func (w *world) Mode() Mode { return w.mode }
func (w *world) Mode() Mode {
if v, ok := w.G.State.Var(VarMode).(string); ok {
return Mode(v)
}
return ModePlay
}
func (w *world) SetMode(m Mode) { w.mode = m }
func (w *world) SetMode(m Mode) { w.G.State.SetVar(VarMode, string(m)) }
func (w *world) HUDVisible() bool { return w.mode == ModePlay }
func (w *world) SetNote(text string) { w.G.State.SetVar(VarNote, text) }
func (w *world) Paused() bool { return w.paused }
func (w *world) Paused() bool { return w.G.State.Var(VarNote) == NotePaused }
func (w *world) Slot2() string {
if v, ok := w.G.State.Var(VarSlot2).(string); ok {