mirror of
https://github.com/prosolis/gogobee.git
synced 2026-09-14 10:51:09 +00:00
adventure: tell Pete who else is on the expedition
Party seats ride the roster detail, beside the supply and threat numbers the same expedition already publishes. Leader first, the hireling named without a token he has no board row for, and an opted-out player's seat kept and anonymised — dropping it would make a party of three read as a pair while the burn rate and enemy scaling beside it still felt three bodies. The board itself was wrong about this: it resolved an expedition with a lookup keyed on dnd_expedition.user_id, which is blind to members, so for the whole life of N3 parties a seated player has shown as idle in town while standing in a dungeon. Pets also send the engine's XP threshold for their current level band, so the web can draw progress without keeping a copy of a curve that would drift.
This commit is contained in:
@@ -460,23 +460,84 @@ func equippedViews(uid id.UserID) []peteclient.ItemView {
|
||||
|
||||
// petViews returns the player's live pet slots. A pet that was chased away is
|
||||
// omitted — it isn't with them right now, and the self-view shows the present.
|
||||
//
|
||||
// XPNeeded rides along so the web can draw the progress toward the next level.
|
||||
// It is the engine's number, not Pete's: the curve steps by level band and a
|
||||
// copy of it on the web side would be a second answer to "how close is my dog"
|
||||
// that drifts the first time the band moves.
|
||||
func petViews(adv *AdventureCharacter) []peteclient.PetView {
|
||||
var out []peteclient.PetView
|
||||
if adv.PetType != "" && !adv.PetChasedAway {
|
||||
out = append(out, peteclient.PetView{
|
||||
Type: adv.PetType, Name: adv.PetName, Level: adv.PetLevel,
|
||||
XP: adv.PetXP, ArmorTier: adv.PetArmorTier,
|
||||
XP: adv.PetXP, XPNeeded: petXPNeededCenti(adv.PetLevel),
|
||||
ArmorTier: adv.PetArmorTier,
|
||||
})
|
||||
}
|
||||
if adv.Pet2Type != "" && !adv.Pet2ChasedAway {
|
||||
out = append(out, peteclient.PetView{
|
||||
Type: adv.Pet2Type, Name: adv.Pet2Name, Level: adv.Pet2Level,
|
||||
XP: adv.Pet2XP, ArmorTier: adv.Pet2ArmorTier,
|
||||
XP: adv.Pet2XP, XPNeeded: petXPNeededCenti(adv.Pet2Level),
|
||||
ArmorTier: adv.Pet2ArmorTier,
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// partySeatViews describes who is on this expedition for the public detail page.
|
||||
// Returns nil for a solo run: expeditionParty always hands back at least the
|
||||
// leader, and a "party" of one chair is a worse thing to draw than nothing.
|
||||
//
|
||||
// The opt-out rule is the Siege contributor's, not the realm occupant's: a seat
|
||||
// belonging to an opted-out player is kept and anonymised. Deleting it would
|
||||
// make a party of three read as a pair, and the numbers beside it — the supply
|
||||
// burn, the threat, the enemy scaling — all felt three bodies. What an unnamed
|
||||
// seat discloses is that somebody else is down there, which the zone and day on
|
||||
// this same page already say about everyone in the party.
|
||||
//
|
||||
// Levels come from a per-seat character load. Parties cap at three and the
|
||||
// companion needs no load at all, so this is at most two extra reads for a
|
||||
// player who is actually in one.
|
||||
func partySeatViews(exp *Expedition) []peteclient.PartySeatView {
|
||||
seats, err := expeditionParty(exp.ID, exp.UserID)
|
||||
if err != nil {
|
||||
slog.Debug("pete: party seats unavailable", "expedition", exp.ID, "err", err)
|
||||
return nil
|
||||
}
|
||||
if len(seats) < 2 {
|
||||
return nil // solo, or a roster that only holds its leader
|
||||
}
|
||||
out := make([]peteclient.PartySeatView, 0, len(seats))
|
||||
for _, s := range seats {
|
||||
if s.Kind == SeatCompanion {
|
||||
// The hireling is named unconditionally: he is not a player, has no
|
||||
// board row to link to and no privacy to protect.
|
||||
out = append(out, peteclient.PartySeatView{
|
||||
Kind: "companion", Name: companionDisplayName,
|
||||
})
|
||||
continue
|
||||
}
|
||||
kind := "member"
|
||||
if s.Kind == SeatLeader {
|
||||
kind = "leader"
|
||||
}
|
||||
v := peteclient.PartySeatView{Kind: kind}
|
||||
if !isNewsOptedOut(s.UserID) {
|
||||
v.Name = charName(s.UserID)
|
||||
if v.Name != "" {
|
||||
// Token only alongside a name: a link to a page that says who they are
|
||||
// would undo the anonymising below all by itself.
|
||||
v.Token = eventToken(s.UserID, "roster")
|
||||
if c, cerr := LoadDnDCharacter(s.UserID); cerr == nil && c != nil {
|
||||
v.Level = c.Level
|
||||
}
|
||||
}
|
||||
}
|
||||
out = append(out, v)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// buildRosterSnapshot assembles the complete board.
|
||||
//
|
||||
// Complete is the contract: Pete *replaces* its board with this, so anyone we
|
||||
@@ -563,7 +624,13 @@ func buildRosterSnapshot(now time.Time, euro *EuroPlugin) (peteclient.RosterSnap
|
||||
|
||||
e.Detail = rosterDetail(pl.uid, c)
|
||||
|
||||
if exp, _ := getActiveExpedition(pl.uid); exp != nil {
|
||||
// activeExpeditionFor, not getActiveExpedition: the latter keys on
|
||||
// dnd_expedition.user_id and is blind to members, so a player seated on
|
||||
// somebody else's run has been reading as "idle in town" on the public board
|
||||
// for the whole life of N3 parties — standing in a tier-4 dungeon. The
|
||||
// expedition it resolves to is the leader's row, which is the right answer:
|
||||
// a party shares one clock, one supply pool and one run.
|
||||
if exp, _, _ := activeExpeditionFor(pl.uid); exp != nil {
|
||||
zone := zoneOrFallback(exp.ZoneID)
|
||||
e.Status = "expedition"
|
||||
e.Zone = zone.Display
|
||||
@@ -576,6 +643,7 @@ func buildRosterSnapshot(now time.Time, euro *EuroPlugin) (peteclient.RosterSnap
|
||||
if e.Detail != nil {
|
||||
e.Detail.Supplies = int(exp.Supplies.Current)
|
||||
e.Detail.ThreatLevel = exp.ThreatLevel
|
||||
e.Detail.Party = partySeatViews(exp)
|
||||
if exp.RunID != "" {
|
||||
if run, rerr := getZoneRun(exp.RunID); rerr == nil && run != nil && run.TotalRooms > 0 {
|
||||
e.Detail.Room = fmt.Sprintf("%d / %d", run.CurrentRoom+1, run.TotalRooms)
|
||||
|
||||
Reference in New Issue
Block a user