package game // pack.c — routines to deal with the pack. // addPack picks up an object and adds it to the pack; if obj is non-nil use // it instead of getting it off the ground (pack.c add_pack). func (g *RogueGame) addPack(obj *Object, silent bool) { p := &g.Player fromFloor := false if obj == nil { if obj = g.Level.ObjectAt(p.Pos.Y, p.Pos.X); obj == nil { return } fromFloor = true } // Check for and deal with scare monster scrolls if g.pickupScareScroll(obj) { return } obj, ok := g.packInsert(obj, fromFloor) if !ok { return } obj.Flags.Set(WasFound) // If this was the object of something's desire, that monster will get // mad and run at the hero. for _, op := range g.Level.Monsters { if op.Dest == &obj.Pos { op.Dest = &p.Pos } } if obj.Kind == KindAmulet { g.HasAmulet = true } // Notify the user if !silent { if !g.Options.Terse { g.addmsgf("you now have ") } g.msg("%s (%c)", g.inventoryName(obj, !g.Options.Terse), obj.PackCh) } } // pickupScareScroll crumbles a found scare monster scroll when it is // picked up again; it reports whether it did (pack.c add_pack). func (g *RogueGame) pickupScareScroll(obj *Object) bool { if obj.Kind != KindScroll || obj.ScrollKind() != ScrollScareMonster || !obj.Flags.Has(WasFound) { return false } p := &g.Player g.Level.RemoveObject(obj) g.mvaddch(p.Pos.Y, p.Pos.X, g.floorCh()) if p.Room.Flags.Has(Gone) { g.Level.SetChar(p.Pos.Y, p.Pos.X, Passage) } else { g.Level.SetChar(p.Pos.Y, p.Pos.X, Floor) } g.msg("the scroll turns to dust as you pick it up") return true } // packInsert places the object in the pack, keeping items of one type // together and merging stackable and grouped items — a translation of // the C linked-list walk in pack.c add_pack. It returns the pack entry // the object ended up as; ok is false when the pack has no room. func (g *RogueGame) packInsert(obj *Object, fromFloor bool) (*Object, bool) { p := &g.Player if len(p.Pack) == 0 { p.Pack = append(p.Pack, obj) obj.PackCh = p.nextPackChar() p.Inpack++ return obj, true } merged := false // lp is the index to insert after; -1 after a merge means no // insertion. i, lp := packScanKind(p.Pack, obj.Kind) if i < len(p.Pack) { i, lp = packScanWhich(p.Pack, obj, i, lp) if op := p.Pack[i]; op.Kind == obj.Kind && op.Which == obj.Which { var ok bool obj, lp, merged, ok = g.packMatch(obj, op, i, fromFloor) if !ok { return nil, false } } } if !merged && lp != -1 { if !g.packRoom(fromFloor, obj) { return nil, false } obj.PackCh = p.nextPackChar() p.Pack = append(p.Pack[:lp+1], append([]*Object{obj}, p.Pack[lp+1:]...)...) } return obj, true } // packScanKind scans to the first pack entry of this kind, returning // its index (or the pack length) and the entry to insert after (the // outer scan of pack.c add_pack). func packScanKind(pack []*Object, kind ObjectKind) (int, int) { lp := -1 i := 0 for ; i < len(pack); i++ { if pack[i].Kind == kind { break } lp = i } return i, lp } // packScanWhich scans within the kind group for the matching subtype // (the inner scan of pack.c add_pack). func packScanWhich(pack []*Object, obj *Object, i, lp int) (int, int) { for pack[i].Kind == obj.Kind && pack[i].Which != obj.Which { lp = i if i+1 >= len(pack) { break } i++ } return i, lp } // packMatch merges the object with a matching pack entry when possible: // stackables merge counts, grouped missiles rejoin their bundle, and // anything else marks the insertion point (the matched-subtype switch // of pack.c add_pack). It returns the resulting entry, the insert-after // index, whether a merge happened, and ok false when the pack is full. func (g *RogueGame) packMatch(obj, op *Object, i int, fromFloor bool) (*Object, int, bool, bool) { switch { case op.Kind.MergesInPack(): if !g.packRoom(fromFloor, obj) { return nil, 0, false, false } op.Count++ return op, -1, true, true case obj.Group != 0: return g.packMatchGroup(obj, i, fromFloor) default: return obj, i, false, true } } // packMatchGroup rejoins a grouped missile bundle with its group entry // (the o_group arm of pack.c add_pack). func (g *RogueGame) packMatchGroup(obj *Object, i int, fromFloor bool) (*Object, int, bool, bool) { p := &g.Player lp := i for p.Pack[i].Kind == obj.Kind && p.Pack[i].Which == obj.Which && p.Pack[i].Group != obj.Group { lp = i if i+1 >= len(p.Pack) { break } i++ } op := p.Pack[i] if op.Kind == obj.Kind && op.Which == obj.Which && op.Group == obj.Group { op.Count += obj.Count p.Inpack-- if !g.packRoom(fromFloor, obj) { return nil, 0, false, false } return op, -1, true, true } return obj, lp, false, true } // packRoom sees if there's room in the pack; if not, prints an appropriate // message (pack.c pack_room). func (g *RogueGame) packRoom(fromFloor bool, obj *Object) bool { p := &g.Player if p.Inpack++; p.Inpack > MaxPack { if !g.Options.Terse { g.addmsgf("there's ") } g.addmsgf("no room") if !g.Options.Terse { g.addmsgf(" in your pack") } g.endmsg() if fromFloor { g.moveMsg(obj) } p.Inpack = MaxPack return false } if fromFloor { g.Level.RemoveObject(obj) g.mvaddch(p.Pos.Y, p.Pos.X, g.floorCh()) if p.Room.Flags.Has(Gone) { g.Level.SetChar(p.Pos.Y, p.Pos.X, Passage) } else { g.Level.SetChar(p.Pos.Y, p.Pos.X, Floor) } } return true } // leavePack takes an item out of the pack (pack.c leave_pack), keeping // the repeat-command bookkeeping; the pack surgery is // Player.removeFromPack. func (g *RogueGame) leavePack(obj *Object, newobj, all bool) *Object { if obj.Count > 1 && !all { g.LastPick = obj } else { g.LastPick = nil } return g.Player.removeFromPack(obj, newobj, all) } // inventory lists what is in the pack; returns true if there is something // of the given type (pack.c inventory). func (g *RogueGame) inventory(list []*Object, kind ObjectKind) bool { g.NObjs = 0 for _, item := range list { if !matchesFilter(kind, item) { continue } g.NObjs++ g.Msgs.MsgEsc = true line := string(item.PackCh) + ") " + g.inventoryName(item, false) if g.addLine("%s", line) == Escape { g.Msgs.MsgEsc = false g.msg("") return true } g.Msgs.MsgEsc = false } if g.NObjs == 0 { if kind == KindNone { g.msg("%s", g.chooseTerse("empty handed", "you are empty handed")) } else { g.msg("%s", g.chooseTerse("nothing appropriate", "you don't have anything appropriate")) } return false } g.endLine() return true } // pickUp adds something to the character's pack (pack.c pick_up). func (g *RogueGame) pickUp(ch byte) { p := &g.Player if p.On(Levitating) { return } obj := g.Level.ObjectAt(p.Pos.Y, p.Pos.X) if g.MoveOn { g.moveMsg(obj) return } switch ch { case Gold: if obj == nil { return } g.money(obj.GoldValue) g.Level.RemoveObject(obj) p.Room.GoldVal = 0 default: g.addPack(nil, false) } } // matchesFilter reports whether an item passes a get_item/inventory kind // filter (the C condition in pack.c inventory, untangled): KindNone takes // everything, KindCallable takes anything nameable (not food, not the // amulet), KindRingOrStick takes rings and wands. func matchesFilter(kind ObjectKind, item *Object) bool { switch kind { case KindNone: return true case KindCallable: return item.Kind != KindFood && item.Kind != KindAmulet case KindRingOrStick: return item.Kind == KindRing || item.Kind == KindWand default: return item.Kind == kind } } // moveMsg prints the message if you are just moving onto an object // (pack.c move_msg). func (g *RogueGame) moveMsg(obj *Object) { if !g.Options.Terse { g.addmsgf("you ") } g.msg("moved onto %s", g.inventoryName(obj, true)) } // pickyInven allows the player to inventory a single item (pack.c // picky_inven). func (g *RogueGame) pickyInven() { p := &g.Player if len(p.Pack) == 0 { g.msg("you aren't carrying anything") return } if len(p.Pack) == 1 { g.msg("a) %s", g.inventoryName(p.Pack[0], false)) return } g.msg("%s", g.chooseTerse("item: ", "which item do you wish to inventory: ")) g.Msgs.Mpos = 0 mch := g.readchar() if mch == Escape { g.msg("") return } for _, obj := range p.Pack { if mch == obj.PackCh { g.msg("%c) %s", mch, g.inventoryName(obj, false)) return } } g.msg("'%s' not in pack", unctrl(mch)) } // promptPackItem picks something out of a pack for a purpose (pack.c // get_item); the second result reports whether the player chose an // item. func (g *RogueGame) promptPackItem(purpose string, kind ObjectKind) (*Object, bool) { p := &g.Player if len(p.Pack) == 0 { g.msg("you aren't carrying anything") return nil, false } if g.Again { return g.repeatLastItem() } for { g.promptItemPurpose(purpose) ch := g.readchar() g.Msgs.Mpos = 0 // Give the poor player a chance to abort the command if ch == Escape { g.resetLast() g.After = false g.msg("") return nil, false } g.NObjs = 1 // normal case: person types one char if ch == '*' { g.Msgs.Mpos = 0 if !g.inventory(p.Pack, kind) { g.After = false return nil, false } continue } for _, obj := range p.Pack { if obj.PackCh == ch { return obj, true } } g.msg("'%s' is not a valid item", unctrl(ch)) } } // repeatLastItem replays the previous selection for the repeat command // (pack.c get_item). func (g *RogueGame) repeatLastItem() (*Object, bool) { if g.LastPick != nil { return g.LastPick, true } g.msg("you ran out") return nil, false } // promptItemPurpose prints the "which object do you want to ...?" // prompt (pack.c get_item). func (g *RogueGame) promptItemPurpose(purpose string) { if !g.Options.Terse { g.addmsgf("which object do you want to ") } g.addmsgf("%s", purpose) if g.Options.Terse { g.addmsgf(" what") } g.msg("? (* for list): ") } // money adds or subtracts gold from the pack (pack.c money). func (g *RogueGame) money(value int) { p := &g.Player p.Purse += value g.mvaddch(p.Pos.Y, p.Pos.X, g.floorCh()) if p.Room.Flags.Has(Gone) { g.Level.SetChar(p.Pos.Y, p.Pos.X, Passage) } else { g.Level.SetChar(p.Pos.Y, p.Pos.X, Floor) } if value > 0 { if !g.Options.Terse { g.addmsgf("you found ") } g.msg("%d gold pieces", value) } } // floorCh returns the appropriate floor character for her room // (pack.c floor_ch). func (g *RogueGame) floorCh() byte { if g.Player.Room.Flags.Has(Gone) { return Passage } if g.showFloor() { return Floor } return ' ' } // floorAt returns the character at the hero's position, taking see_floor // into account (pack.c floor_at). func (g *RogueGame) floorAt() byte { ch := g.Level.Char(g.Player.Pos.Y, g.Player.Pos.X) if ch == Floor { ch = g.floorCh() } return ch } // resetLast resets the last command when the current one is aborted // (pack.c reset_last). func (g *RogueGame) resetLast() { g.LastComm = g.LLastComm g.LastDir = g.LLastDir g.LastPick = g.LLastPick } // chooseTerse picks the terse or verbose variant of a message. func (g *RogueGame) chooseTerse(terse, verbose string) string { if g.Options.Terse { return terse } return verbose }