refact
This commit is contained in:
20
lib/assets.go
Normal file
20
lib/assets.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package lib
|
||||
|
||||
import p "git.teletypegames.org/games/pncdsl"
|
||||
|
||||
// Assets lists every asset the game references. Files don't need to
|
||||
// exist on disk — the library substitutes deterministic colored
|
||||
// placeholders for anything missing.
|
||||
var Assets = []p.Asset{
|
||||
{Name: "bg/bedroom", Path: "assets/bg/bedroom.png", Kind: p.AssetImage},
|
||||
{Name: "bg/kitchen", Path: "assets/bg/kitchen.png", Kind: p.AssetImage},
|
||||
{Name: "spr/key", Path: "assets/spr/key.png", Kind: p.AssetImage},
|
||||
{Name: "spr/beans", Path: "assets/spr/beans.png", Kind: p.AssetImage},
|
||||
{Name: "spr/mug", Path: "assets/spr/mug.png", Kind: p.AssetImage},
|
||||
{Name: "spr/player", Path: "assets/spr/player.png", Kind: p.AssetImage},
|
||||
{Name: "spr/cat", Path: "assets/spr/cat.png", Kind: p.AssetImage},
|
||||
|
||||
{Name: "mus/wakeup", Path: "assets/mus/wakeup.ogg", Kind: p.AssetAudio},
|
||||
{Name: "mus/calm", Path: "assets/mus/calm.ogg", Kind: p.AssetAudio},
|
||||
{Name: "snd/coffee_done", Path: "assets/snd/coffee_done.ogg", Kind: p.AssetAudio},
|
||||
}
|
||||
29
lib/build_test.go
Normal file
29
lib/build_test.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package lib
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestBuildValidates is the headless smoke test: it composes the full
|
||||
// game and asks the library to cross-check every name reference
|
||||
// between managers. No window opens.
|
||||
func TestBuildValidates(t *testing.T) {
|
||||
g := Build()
|
||||
if err := g.Validate(); err != nil {
|
||||
t.Fatalf("validate: %v", err)
|
||||
}
|
||||
for _, name := range []string{"bedroom", "kitchen"} {
|
||||
if !g.SceneManager.Has(name) {
|
||||
t.Errorf("missing scene %q", name)
|
||||
}
|
||||
}
|
||||
for _, name := range []string{"key", "beans", "mug"} {
|
||||
if !g.ItemManager.Has(name) {
|
||||
t.Errorf("missing item %q", name)
|
||||
}
|
||||
}
|
||||
if !g.DialogueManager.Has("cat_morning") {
|
||||
t.Errorf("missing dialogue cat_morning")
|
||||
}
|
||||
if !g.ScriptManager.Has("intro") || !g.ScriptManager.Has("victory") {
|
||||
t.Errorf("missing intro/victory script")
|
||||
}
|
||||
}
|
||||
16
lib/character.cat.go
Normal file
16
lib/character.cat.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
p "git.teletypegames.org/games/pncdsl"
|
||||
)
|
||||
|
||||
var Cat = p.Character{
|
||||
Name: "cat",
|
||||
Sprite: "spr/cat",
|
||||
Speed: 40,
|
||||
SpeechColor: color.RGBA{200, 200, 255, 255},
|
||||
W: 34,
|
||||
H: 20,
|
||||
}
|
||||
16
lib/character.player.go
Normal file
16
lib/character.player.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
|
||||
p "git.teletypegames.org/games/pncdsl"
|
||||
)
|
||||
|
||||
var Player = p.Character{
|
||||
Name: "player",
|
||||
Sprite: "spr/player",
|
||||
Speed: 80,
|
||||
SpeechColor: color.RGBA{255, 230, 160, 255},
|
||||
W: 28,
|
||||
H: 62,
|
||||
}
|
||||
43
lib/dialog.cat_morning.go
Normal file
43
lib/dialog.cat_morning.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package lib
|
||||
|
||||
import p "git.teletypegames.org/games/pncdsl"
|
||||
|
||||
var CatMorning = p.Dialogue{
|
||||
Name: "cat_morning",
|
||||
Start: "start",
|
||||
Nodes: []p.DialogueNode{
|
||||
{
|
||||
Name: "start",
|
||||
Lines: []p.DialogueLine{
|
||||
{Speaker: "cat", Text: "Mióóóóóóóóu. Késő van."},
|
||||
},
|
||||
Choices: []p.DialogueChoice{
|
||||
{
|
||||
Text: "Mindjárt megy a kávé.",
|
||||
Actions: []p.Action{p.SetFlag(FlagPromisedCoffee), p.EndDialogue()},
|
||||
},
|
||||
{
|
||||
Text: "Te etted meg a babot?",
|
||||
Show: p.Not(p.Flag(FlagCupboardOpen)),
|
||||
Actions: []p.Action{p.GotoNode("beans")},
|
||||
},
|
||||
{
|
||||
Text: "Hagyj békén.",
|
||||
Actions: []p.Action{p.EndDialogue()},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "beans",
|
||||
Lines: []p.DialogueLine{
|
||||
{Speaker: "cat", Text: "A szekrényben van. Mindig ott szokott lenni."},
|
||||
},
|
||||
Choices: []p.DialogueChoice{
|
||||
{
|
||||
Text: "Köszi.",
|
||||
Actions: []p.Action{p.EndDialogue()},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
10
lib/flags.go
Normal file
10
lib/flags.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package lib
|
||||
|
||||
const (
|
||||
FlagKeyTaken = "key_taken"
|
||||
FlagCupboardOpen = "cupboard_open"
|
||||
FlagMugTaken = "mug_taken"
|
||||
FlagCoffeeHasBeans = "coffee_has_beans"
|
||||
FlagCoffeeHasMug = "coffee_has_mug"
|
||||
FlagPromisedCoffee = "promised_coffee"
|
||||
)
|
||||
9
lib/item.beans.go
Normal file
9
lib/item.beans.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package lib
|
||||
|
||||
import p "git.teletypegames.org/games/pncdsl"
|
||||
|
||||
var Beans = p.Item{
|
||||
Name: "beans",
|
||||
Sprite: "spr/beans",
|
||||
Description: "őrölt kávébab",
|
||||
}
|
||||
18
lib/item.key.go
Normal file
18
lib/item.key.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package lib
|
||||
|
||||
import p "git.teletypegames.org/games/pncdsl"
|
||||
|
||||
var Key = p.Item{
|
||||
Name: "key",
|
||||
Sprite: "spr/key",
|
||||
Description: "rozsdás kulcs",
|
||||
OnUseWith: map[string]p.Action{
|
||||
"cupboard": p.Seq(
|
||||
p.Say("player", "Klikk."),
|
||||
p.SetFlag(FlagCupboardOpen),
|
||||
p.Give("beans"),
|
||||
p.TakeAway("key"),
|
||||
p.Say("player", "Bab van benne. Pont, ami kell."),
|
||||
),
|
||||
},
|
||||
}
|
||||
9
lib/item.mug.go
Normal file
9
lib/item.mug.go
Normal file
@@ -0,0 +1,9 @@
|
||||
package lib
|
||||
|
||||
import p "git.teletypegames.org/games/pncdsl"
|
||||
|
||||
var Mug = p.Item{
|
||||
Name: "mug",
|
||||
Sprite: "spr/mug",
|
||||
Description: "kedvenc bögréje",
|
||||
}
|
||||
70
lib/scene.bedroom.go
Normal file
70
lib/scene.bedroom.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package lib
|
||||
|
||||
import p "git.teletypegames.org/games/pncdsl"
|
||||
|
||||
var Bedroom = p.Scene{
|
||||
Name: "bedroom",
|
||||
Title: "Hálószoba",
|
||||
Background: "bg/bedroom",
|
||||
Music: "mus/wakeup",
|
||||
Actors: []p.SceneActor{
|
||||
{CharacterName: "player", At: p.Point{X: 160, Y: 145}},
|
||||
},
|
||||
// Floor strip — Walk targets get clipped to this band, so clicks
|
||||
// on the bed or nightstand send the player to the floor in front
|
||||
// of them instead of letting them clip through furniture.
|
||||
Walkboxes: []p.Polygon{
|
||||
p.Poly(
|
||||
p.Point{X: 10, Y: 135},
|
||||
p.Point{X: 310, Y: 135},
|
||||
p.Point{X: 310, Y: 160},
|
||||
p.Point{X: 10, Y: 160},
|
||||
),
|
||||
},
|
||||
Hotspots: []p.Hotspot{
|
||||
{
|
||||
Name: "bed",
|
||||
Area: p.Rect(20, 70, 90, 60),
|
||||
Label: "ágy",
|
||||
OnLook: p.Say("player", "A párnám még meleg."),
|
||||
OnUse: p.Say("player", "Most keltem fel. Nem alszom vissza."),
|
||||
},
|
||||
{
|
||||
Name: "nightstand",
|
||||
Area: p.Rect(115, 80, 35, 50),
|
||||
Label: "éjjeliszekrény",
|
||||
OnLook: p.If(p.Flag(FlagKeyTaken),
|
||||
p.Say("player", "Üres a tetején."),
|
||||
p.Say("player", "Egy rozsdás kulcs az éjjeliszekrényemen."),
|
||||
),
|
||||
OnTake: p.If(p.Flag(FlagKeyTaken),
|
||||
p.Say("player", "Már elvittem."),
|
||||
p.Seq(
|
||||
p.Walk("player", p.Point{X: 130, Y: 145}),
|
||||
p.Give("key"),
|
||||
p.SetFlag(FlagKeyTaken),
|
||||
p.Say("player", "Pont jól jöhet."),
|
||||
),
|
||||
),
|
||||
OnUse: p.If(p.Flag(FlagKeyTaken),
|
||||
p.Say("player", "Már elvittem a kulcsot."),
|
||||
p.Seq(
|
||||
p.Walk("player", p.Point{X: 130, Y: 145}),
|
||||
p.Give("key"),
|
||||
p.SetFlag(FlagKeyTaken),
|
||||
p.Say("player", "Megvan a kulcs."),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "door",
|
||||
Area: p.Rect(260, 50, 40, 90),
|
||||
Label: "ajtó",
|
||||
OnLook: p.Say("player", "Az ajtó a konyhába vezet."),
|
||||
OnUse: p.Seq(
|
||||
p.Walk("player", p.Point{X: 260, Y: 145}),
|
||||
p.GoTo("kitchen"),
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
135
lib/scene.kitchen.go
Normal file
135
lib/scene.kitchen.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package lib
|
||||
|
||||
import p "git.teletypegames.org/games/pncdsl"
|
||||
|
||||
var Kitchen = p.Scene{
|
||||
Name: "kitchen",
|
||||
Title: "Konyha",
|
||||
Background: "bg/kitchen",
|
||||
Music: "mus/calm",
|
||||
Actors: []p.SceneActor{
|
||||
{CharacterName: "player", At: p.Point{X: 60, Y: 145}},
|
||||
{CharacterName: "cat", At: p.Point{X: 215, Y: 142}},
|
||||
},
|
||||
// Floor strip. Same shape as the bedroom — keeps movement on a
|
||||
// consistent Y band and clips destinations away from the cupboard
|
||||
// and coffee machine.
|
||||
Walkboxes: []p.Polygon{
|
||||
p.Poly(
|
||||
p.Point{X: 10, Y: 135},
|
||||
p.Point{X: 310, Y: 135},
|
||||
p.Point{X: 310, Y: 160},
|
||||
p.Point{X: 10, Y: 160},
|
||||
),
|
||||
},
|
||||
// First time the player walks into the kitchen with the key in
|
||||
// hand, drop a hint. The trigger arms on scene enter; the
|
||||
// rising-edge fires once per arming because Once: true.
|
||||
Triggers: []p.Trigger{
|
||||
{
|
||||
Name: "kitchen_with_key_hint",
|
||||
Once: true,
|
||||
When: p.And(p.HasItem("key"), p.Not(p.Flag(FlagCupboardOpen))),
|
||||
Do: p.Say("player", "A kulcs jól jöhet a szekrényhez."),
|
||||
},
|
||||
},
|
||||
Hotspots: []p.Hotspot{
|
||||
{
|
||||
Name: "cupboard",
|
||||
Area: p.Rect(30, 40, 80, 80),
|
||||
Label: "szekrény",
|
||||
OnLook: p.Say("player", "Konyhaszekrény. Zárva."),
|
||||
OnUse: p.If(p.Flag(FlagCupboardOpen),
|
||||
p.Say("player", "Már nyitva van."),
|
||||
p.If(p.HasItem("key"),
|
||||
p.Seq(
|
||||
p.Walk("player", p.Point{X: 70, Y: 145}),
|
||||
p.Say("player", "Klikk."),
|
||||
p.SetFlag(FlagCupboardOpen),
|
||||
p.Give("beans"),
|
||||
p.TakeAway("key"),
|
||||
),
|
||||
p.Say("player", "Zárva. Kéne egy kulcs."),
|
||||
),
|
||||
),
|
||||
OnUseWith: map[string]p.Action{
|
||||
"key": p.Seq(
|
||||
p.Walk("player", p.Point{X: 70, Y: 145}),
|
||||
p.Say("player", "Klikk."),
|
||||
p.SetFlag(FlagCupboardOpen),
|
||||
p.Give("beans"),
|
||||
p.TakeAway("key"),
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "shelf",
|
||||
Area: p.Rect(115, 45, 35, 50),
|
||||
Label: "polc",
|
||||
OnLook: p.Say("player", "A polc tele bögrékkel."),
|
||||
OnTake: p.If(p.Flag(FlagMugTaken),
|
||||
p.Say("player", "Tiszta már mind elfogyott."),
|
||||
p.Seq(
|
||||
p.Walk("player", p.Point{X: 125, Y: 145}),
|
||||
p.Give("mug"),
|
||||
p.SetFlag(FlagMugTaken),
|
||||
p.Say("player", "Egy bögre, kérem."),
|
||||
),
|
||||
),
|
||||
OnUse: p.If(p.Flag(FlagMugTaken),
|
||||
p.Say("player", "Már elvettem egyet."),
|
||||
p.Seq(
|
||||
p.Walk("player", p.Point{X: 125, Y: 145}),
|
||||
p.Give("mug"),
|
||||
p.SetFlag(FlagMugTaken),
|
||||
p.Say("player", "Egy bögre, kérem."),
|
||||
),
|
||||
),
|
||||
},
|
||||
{
|
||||
Name: "coffee_machine",
|
||||
Area: p.Rect(160, 75, 50, 55),
|
||||
Label: "kávéfőző",
|
||||
OnLook: p.Say("player", "A jó öreg kávéfőző."),
|
||||
OnUse: p.If(p.And(p.Flag(FlagCoffeeHasBeans), p.Flag(FlagCoffeeHasMug)),
|
||||
p.RunScript("victory"),
|
||||
p.Say("player", "Még hiányzik valami. Bab? Bögre?"),
|
||||
),
|
||||
OnUseWith: map[string]p.Action{
|
||||
"beans": p.Seq(
|
||||
p.Walk("player", p.Point{X: 180, Y: 145}),
|
||||
p.SetFlag(FlagCoffeeHasBeans),
|
||||
p.TakeAway("beans"),
|
||||
p.Say("player", "Bab a helyén."),
|
||||
p.If(p.And(p.Flag(FlagCoffeeHasBeans), p.Flag(FlagCoffeeHasMug)),
|
||||
p.RunScript("victory")),
|
||||
),
|
||||
"mug": p.Seq(
|
||||
p.Walk("player", p.Point{X: 180, Y: 145}),
|
||||
p.SetFlag(FlagCoffeeHasMug),
|
||||
p.TakeAway("mug"),
|
||||
p.Say("player", "Bögre a helyén."),
|
||||
p.If(p.And(p.Flag(FlagCoffeeHasBeans), p.Flag(FlagCoffeeHasMug)),
|
||||
p.RunScript("victory")),
|
||||
),
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "cat",
|
||||
Area: p.Rect(195, 118, 35, 22),
|
||||
Label: "Cirmos",
|
||||
OnLook: p.Say("player", "Cirmos, a házikedvencem."),
|
||||
OnTalk: p.RunDialogue("cat_morning"),
|
||||
},
|
||||
{
|
||||
Name: "door",
|
||||
Area: p.Rect(0, 40, 22, 90),
|
||||
Label: "ajtó",
|
||||
OnLook: p.Say("player", "Vissza a hálóba."),
|
||||
OnUse: p.Seq(
|
||||
p.Walk("player", p.Point{X: 20, Y: 145}),
|
||||
p.GoTo("bedroom"),
|
||||
),
|
||||
},
|
||||
},
|
||||
}
|
||||
14
lib/script.intro.go
Normal file
14
lib/script.intro.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package lib
|
||||
|
||||
import p "git.teletypegames.org/games/pncdsl"
|
||||
|
||||
var IntroScript = p.Script{
|
||||
Name: "intro",
|
||||
Actions: p.Seq(
|
||||
p.Wait(0.4),
|
||||
p.Say("player", "Brr, hideg van."),
|
||||
p.Say("player", "Egy kávé kéne, mielőtt szétszakad a fejem."),
|
||||
p.Walk("player", p.Point{X: 200, Y: 145}),
|
||||
p.SetVar("player.mood", "Eltökélt"),
|
||||
),
|
||||
}
|
||||
18
lib/script.victory.go
Normal file
18
lib/script.victory.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package lib
|
||||
|
||||
import p "git.teletypegames.org/games/pncdsl"
|
||||
|
||||
var VictoryScript = p.Script{
|
||||
Name: "victory",
|
||||
Actions: p.Seq(
|
||||
p.Say("player", "Készen is van!"),
|
||||
p.PlaySound("snd/coffee_done"),
|
||||
p.Wait(0.6),
|
||||
p.Say("cat", "Mióóóóu. *boldog macska*"),
|
||||
p.Say("player", "Reggeli kávé: megmentve."),
|
||||
p.SetVar("score", 100),
|
||||
p.SetVar("player.mood", "Boldog"),
|
||||
p.SetVar("cat.mood", "Boldog"),
|
||||
p.ShowEnd("Vége — kösz, hogy játszottál!"),
|
||||
),
|
||||
}
|
||||
82
lib/setup.go
Normal file
82
lib/setup.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
p "git.teletypegames.org/games/pncdsl"
|
||||
)
|
||||
|
||||
// Run builds the game and hands it to the pncdsl engine. It is the
|
||||
// single entry point referenced from main.go.
|
||||
func Run() {
|
||||
if err := p.Run(Build()); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Build wires every entity declared in the sibling files into a fresh
|
||||
// *Game. Validate() runs from p.Run() to cross-check name references
|
||||
// between managers.
|
||||
func Build() *p.Game {
|
||||
g := p.NewGame("Reggeli Kávé", 320, 200)
|
||||
|
||||
for _, a := range Assets {
|
||||
g.AssetManager.Register(a)
|
||||
}
|
||||
|
||||
g.CharacterManager.Register(Player)
|
||||
g.CharacterManager.Register(Cat)
|
||||
|
||||
g.ItemManager.Register(Key)
|
||||
g.ItemManager.Register(Beans)
|
||||
g.ItemManager.Register(Mug)
|
||||
|
||||
g.DialogueManager.Register(CatMorning)
|
||||
|
||||
g.ScriptManager.Register(IntroScript)
|
||||
g.ScriptManager.Register(VictoryScript)
|
||||
|
||||
g.SceneManager.Register(Bedroom)
|
||||
g.SceneManager.Register(Kitchen)
|
||||
|
||||
// Story-rich HUD: top bar + NPC character panel + chat log + permanent
|
||||
// verb wheel + small inventory. The player panel is skipped — the
|
||||
// player avatar is always visible in the scene, so the floating card
|
||||
// in the corner just gets in the way. Pass "player" as the second
|
||||
// arg to put it back.
|
||||
p.RegisterRichUI(g, "", "cat")
|
||||
// Swap the rich UI's always-visible verb wheel for a right-click,
|
||||
// hotspot-only verb coin. The default wheel sits over the bedroom
|
||||
// door, blocking that exit — the coin only appears on a right-click
|
||||
// landing on a hotspot, so the screen stays free between picks.
|
||||
// Drop the cursor too so we can re-register it after the new wheel,
|
||||
// keeping the crosshair painted on top of the open coin.
|
||||
g.UIManager.Remove("verbs")
|
||||
g.UIManager.Remove("cursor")
|
||||
g.UIManager.Register(&p.RadialVerbs{
|
||||
Name: "verbs",
|
||||
Trigger: p.MouseButtonRight,
|
||||
Radius: 36,
|
||||
HotspotOnly: true,
|
||||
Labels: map[string]string{
|
||||
"look": "Néz",
|
||||
"use": "Tedd",
|
||||
"talk": "Szól",
|
||||
"take": "Vedd",
|
||||
},
|
||||
})
|
||||
g.UIManager.Register(&p.Cursor{Name: "cursor"})
|
||||
g.UIManager.Register(&SaveKeys{})
|
||||
g.MaxLogLines = 64
|
||||
|
||||
// initial HUD vars
|
||||
g.State.SetVar("score", 0)
|
||||
g.State.SetVar("time", "Nap 1 - 07:23")
|
||||
g.State.SetVar("player.state", "Ébren")
|
||||
g.State.SetVar("player.mood", "Álmos")
|
||||
g.State.SetVar("cat.state", "Várakozik")
|
||||
g.State.SetVar("cat.mood", "Türelmetlen")
|
||||
|
||||
g.StartAt("bedroom").OnStart(p.RunScript("intro"))
|
||||
return g
|
||||
}
|
||||
35
lib/widget.savekeys.go
Normal file
35
lib/widget.savekeys.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package lib
|
||||
|
||||
import (
|
||||
"github.com/hajimehoshi/ebiten/v2"
|
||||
"github.com/hajimehoshi/ebiten/v2/inpututil"
|
||||
|
||||
p "git.teletypegames.org/games/pncdsl"
|
||||
)
|
||||
|
||||
// SaveKeys is a no-draw widget that binds F5 → Save(slot 0) and
|
||||
// F9 → Load(slot 0). Errors are surfaced via FlashLine so the player
|
||||
// sees them in the status strip.
|
||||
type SaveKeys struct{}
|
||||
|
||||
func (s *SaveKeys) GetName() string { return "save_keys" }
|
||||
|
||||
func (s *SaveKeys) Tick(ctx *p.UICtx) {
|
||||
g := ctx.Game
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyF5) {
|
||||
if err := g.Save(0); err != nil {
|
||||
g.FlashLine("Mentés hiba: " + err.Error())
|
||||
} else {
|
||||
g.FlashLine("Mentve (F5).")
|
||||
}
|
||||
}
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyF9) {
|
||||
if err := g.Load(0); err != nil {
|
||||
g.FlashLine("Töltés hiba: " + err.Error())
|
||||
} else {
|
||||
g.FlashLine("Betöltve (F9).")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SaveKeys) Draw(_ *ebiten.Image, _ *p.UICtx) {}
|
||||
Reference in New Issue
Block a user