mirror of
https://github.com/prosolis/gogobee.git
synced 2026-07-15 16:42:41 +00:00
Compare commits
2 Commits
4d35c0010e
...
forex-cryp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ea3e42612 | ||
|
|
3ed2e1d8e0 |
4
go.mod
4
go.mod
@@ -12,7 +12,7 @@ require (
|
||||
github.com/olebedev/when v1.1.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||
golang.org/x/image v0.43.0
|
||||
golang.org/x/image v0.40.0
|
||||
maunium.net/go/mautrix v0.28.0
|
||||
modernc.org/sqlite v1.50.1
|
||||
)
|
||||
@@ -40,7 +40,7 @@ require (
|
||||
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect
|
||||
golang.org/x/net v0.54.0 // indirect
|
||||
golang.org/x/sys v0.44.0 // indirect
|
||||
golang.org/x/text v0.38.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
modernc.org/libc v1.72.3 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.11.0 // indirect
|
||||
|
||||
12
go.sum
12
go.sum
@@ -85,8 +85,8 @@ golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw=
|
||||
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw=
|
||||
golang.org/x/image v0.43.0 h1:FLxcP4ec2350nTfOC8ysKtqYSIFbk/QGjw1ZHNP4tsY=
|
||||
golang.org/x/image v0.43.0/go.mod h1:rrpelvGFt+kLPAjPM4HeWPgrl0FtafueU//e5N0qk/Q=
|
||||
golang.org/x/image v0.40.0 h1:Tw4GyDXMo+daZN1znreBRC3VayR1aLFUyUEOLUdW1a8=
|
||||
golang.org/x/image v0.40.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||
@@ -112,8 +112,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM=
|
||||
golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -146,8 +146,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.38.0 h1:sXmwo9DwP3OK9EZ7PqAdaooSGozfl/3a6/xJcbzPRhE=
|
||||
golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
|
||||
@@ -206,6 +206,25 @@ func petShouldArrive(pet PetState, house HouseState) bool {
|
||||
return rand.Float64() < 0.30
|
||||
}
|
||||
|
||||
// maybeRollPetArrivalOnEmerge rolls the pet-arrival check when a player
|
||||
// surfaces from an expedition (voluntary extract, abandon, or a survived
|
||||
// forced extraction) or revives after death. The arrival roll lives on the
|
||||
// emergence seam — not the legacy 08:00 overworld morning DM — because
|
||||
// expedition players are almost never in the overworld at the scheduled hour,
|
||||
// so the morning roll never reached them. Story beat: while the player was
|
||||
// underground, an animal wandered into the empty house looking for food.
|
||||
//
|
||||
// Safe to call unconditionally on any emergence: petShouldArrive gates on
|
||||
// house tier / not-yet-arrived, and petArrivalDM won't clobber an existing
|
||||
// pending interaction.
|
||||
func (p *AdventurePlugin) maybeRollPetArrivalOnEmerge(userID id.UserID) {
|
||||
pet, _ := loadPetState(userID)
|
||||
house, _ := loadHouseState(userID)
|
||||
if petShouldArrive(pet, house) {
|
||||
p.petArrivalDM(userID)
|
||||
}
|
||||
}
|
||||
|
||||
// petArrivalDM sends the initial "there's an animal in your house" DM.
|
||||
func (p *AdventurePlugin) petArrivalDM(userID id.UserID) {
|
||||
// Don't overwrite an existing pending interaction
|
||||
|
||||
@@ -82,6 +82,12 @@ func (p *AdventurePlugin) sendMorningDMs() {
|
||||
if err := p.SendDM(char.UserID, text); err != nil {
|
||||
slog.Error("adventure: failed to send respawn DM", "user", char.UserID, "err", err)
|
||||
}
|
||||
|
||||
// Emergence seam (death case): a player who died underground
|
||||
// "comes home" on respawn. This is the deferred half of the
|
||||
// emergence roll — survived extractions roll at their exit
|
||||
// site; deaths roll here. See maybeRollPetArrivalOnEmerge.
|
||||
p.maybeRollPetArrivalOnEmerge(char.UserID)
|
||||
}
|
||||
|
||||
// Babysitting: pet-care trickle (no harvest actions; safe-rest perk
|
||||
@@ -133,13 +139,12 @@ func (p *AdventurePlugin) sendMorningDMs() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Pet arrival check (fires before normal morning DM)
|
||||
house, _ := loadHouseState(char.UserID)
|
||||
// Pet arrival no longer rolls here. The 08:00 overworld morning DM
|
||||
// is skipped for anyone underground (expedition gate above), so it
|
||||
// never reached expedition players. Arrival now fires on the
|
||||
// emergence seam — see maybeRollPetArrivalOnEmerge, called from the
|
||||
// extract/abandon/forced-extract and respawn paths.
|
||||
pet, _ := loadPetState(char.UserID)
|
||||
if petShouldArrive(pet, house) {
|
||||
p.petArrivalDM(char.UserID)
|
||||
continue
|
||||
}
|
||||
|
||||
// Morning pet event
|
||||
petEvent := petMorningEvent(pet)
|
||||
|
||||
@@ -486,9 +486,14 @@ func (p *AdventurePlugin) expeditionCmdAbandon(ctx MessageContext) error {
|
||||
}
|
||||
_ = retireAllRegionRuns(exp)
|
||||
_ = appendExpeditionLog(exp.ID, exp.CurrentDay, "narrative", "expedition abandoned", "")
|
||||
return p.SendDM(ctx.Sender, fmt.Sprintf(
|
||||
if err := p.SendDM(ctx.Sender, fmt.Sprintf(
|
||||
"Expedition in **%s** abandoned on Day %d. Supplies are forfeit. The dungeon remembers.",
|
||||
zone.Display, exp.CurrentDay))
|
||||
zone.Display, exp.CurrentDay)); err != nil {
|
||||
return err
|
||||
}
|
||||
// Emergence seam: see maybeRollPetArrivalOnEmerge.
|
||||
p.maybeRollPetArrivalOnEmerge(ctx.Sender)
|
||||
return nil
|
||||
}
|
||||
|
||||
// helper: ensure we don't shadow id.UserID import in test harness.
|
||||
|
||||
@@ -303,6 +303,14 @@ func (p *AdventurePlugin) deliverBriefing(e *Expedition, now time.Time) error {
|
||||
if err := p.SendDM(uid, body); err != nil {
|
||||
slog.Warn("expedition: send briefing DM", "user", uid, "err", err)
|
||||
}
|
||||
// Emergence seam: a briefing-time forced extraction (starvation /
|
||||
// abyss collapse) surfaces the player alive — roll pet arrival.
|
||||
// Combat/patrol deaths never reach deliverBriefing (the row is
|
||||
// already abandoned), so an abandoned status here means a survived
|
||||
// emergence; those death paths roll on respawn instead.
|
||||
if e.Status == ExpeditionStatusAbandoned {
|
||||
p.maybeRollPetArrivalOnEmerge(uid)
|
||||
}
|
||||
}
|
||||
if err := appendExpeditionLog(e.ID, e.CurrentDay, "briefing",
|
||||
fmt.Sprintf("morning briefing — %.1f SU consumed overnight", burn), line); err != nil {
|
||||
|
||||
@@ -179,7 +179,13 @@ func (p *AdventurePlugin) handleExtractCmd(ctx MessageContext, _ string) error {
|
||||
}
|
||||
b.WriteString(fmt.Sprintf("Loot, XP, and coins are kept. The dungeon stays where you left it — `!resume` within 7 days to come back. After %s the expedition expires.",
|
||||
(time.Now().UTC().Add(extractResumeWindow)).Format("2006-01-02 15:04 MST")))
|
||||
return p.SendDM(ctx.Sender, b.String())
|
||||
if err := p.SendDM(ctx.Sender, b.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
// Emergence seam: surfacing from a run is when an animal may have moved
|
||||
// into the empty house.
|
||||
p.maybeRollPetArrivalOnEmerge(ctx.Sender)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ── !resume command ─────────────────────────────────────────────────────────
|
||||
|
||||
@@ -122,6 +122,10 @@ const fxHelpText = "**Forex Commands**\n\n" +
|
||||
// ── Command Handlers ────────────────────────────────────────────────────────
|
||||
|
||||
func (p *ForexPlugin) cmdRate(ctx MessageContext, args []string) error {
|
||||
if msg := fxValidateAnalysisArgs(args); msg != "" {
|
||||
return p.SendReply(ctx.RoomID, ctx.EventID, msg)
|
||||
}
|
||||
|
||||
// Check for cross-pair syntax like EUR/USD or USD/JPY
|
||||
if pair := fxParsePair(args, false); pair != nil {
|
||||
return p.cmdPairRateOrReport(ctx, pair, false)
|
||||
@@ -277,6 +281,9 @@ func (p *ForexPlugin) fxPerUSD(cur string, fiatRates map[string]float64) (float6
|
||||
}
|
||||
|
||||
func (p *ForexPlugin) cmdReport(ctx MessageContext, args []string) error {
|
||||
if msg := fxValidateAnalysisArgs(args); msg != "" {
|
||||
return p.SendReply(ctx.RoomID, ctx.EventID, msg)
|
||||
}
|
||||
if pair := fxParsePair(args, false); pair != nil {
|
||||
return p.cmdPairRateOrReport(ctx, pair, true)
|
||||
}
|
||||
@@ -404,6 +411,34 @@ func (p *ForexPlugin) checkAlerts(rates map[string]float64) {
|
||||
|
||||
// ── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
// fxValidateAnalysisArgs vets the currency tokens passed to the fiat-only
|
||||
// rate/report path. It returns a player-facing error message if any token is
|
||||
// unusable here, or "" when the args are fine (including empty args, which fall
|
||||
// back to the default tracked set). Crypto tokens get a dedicated nudge since
|
||||
// crypto only works on the conversion path; anything else is just invalid.
|
||||
func fxValidateAnalysisArgs(args []string) string {
|
||||
for _, a := range args {
|
||||
// Split pair syntax (BTC/USD) into its sides so each is checked.
|
||||
raw := strings.ToUpper(a)
|
||||
toks := []string{raw}
|
||||
for _, sep := range []string{"/", "|", "-"} {
|
||||
if strings.Contains(raw, sep) {
|
||||
toks = strings.SplitN(raw, sep, 2)
|
||||
break
|
||||
}
|
||||
}
|
||||
for _, t := range toks {
|
||||
if fxIsCrypto(t) {
|
||||
return "Silly rabbit. Crypto is for kids! (In other words.. that's an invalid command — crypto only works for conversions like `!fx 100 USD to BTC`.)"
|
||||
}
|
||||
if !fxIsSupported(t) {
|
||||
return fmt.Sprintf("Don't know the currency `%s`. Try `!fx help`.", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func fxParseCurrencies(args []string) []string {
|
||||
var out []string
|
||||
for _, a := range args {
|
||||
|
||||
Reference in New Issue
Block a user