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.findObj(p.Pos.Y, p.Pos.X); obj == nil { return } fromFloor = true } // Check for and deal with scare monster scrolls if obj.Kind == KindScroll && obj.ScrollKind() == ScrollScareMonster && obj.Flags.Has(WasFound) { detachObj(&g.Level.Objects, 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 } if len(p.Pack) == 0 { p.Pack = append(p.Pack, obj) obj.PackCh = g.packChar() p.Inpack++ } else { // Walk the pack looking for the insertion point, keeping items of // one type together and merging stackable/grouped items — a direct // translation of the C linked-list walk. lp is the index to insert // after; -1 after a merge means no insertion. lp := -1 merged := false for i := 0; i < len(p.Pack); i++ { if p.Pack[i].Kind != obj.Kind { lp = i continue } // found the group of our type: scan for matching subtype for p.Pack[i].Kind == obj.Kind && p.Pack[i].Which != obj.Which { lp = i if i+1 >= len(p.Pack) { break } i++ } op := p.Pack[i] if op.Kind == obj.Kind && op.Which == obj.Which { if op.Kind.MergesInPack() { if !g.packRoom(fromFloor, obj) { return } op.Count++ obj = op lp = -1 merged = true } else if obj.Group != 0 { 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 } obj = op lp = -1 merged = true } } else { lp = i } } break } if !merged && lp != -1 { if !g.packRoom(fromFloor, obj) { return } obj.PackCh = g.packChar() p.Pack = append(p.Pack[:lp+1], append([]*Object{obj}, p.Pack[lp+1:]...)...) } } 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.addmsg("you now have ") } g.msg("%s (%c)", g.invName(obj, !g.Options.Terse), obj.PackCh) } } // 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.addmsg("there's ") } g.addmsg("no room") if !g.Options.Terse { g.addmsg(" in your pack") } g.endmsg() if fromFloor { g.moveMsg(obj) } p.Inpack = MaxPack return false } if fromFloor { detachObj(&g.Level.Objects, 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). func (g *RogueGame) leavePack(obj *Object, newobj, all bool) *Object { p := &g.Player p.Inpack-- nobj := obj if obj.Count > 1 && !all { g.LastPick = obj obj.Count-- if obj.Group != 0 { p.Inpack++ } if newobj { copied := *obj nobj = &copied nobj.Count = 1 } } else { g.LastPick = nil p.PackUsed[obj.PackCh-'a'] = false detachObj(&p.Pack, obj) } return nobj } // packChar returns the next unused pack character (pack.c pack_char). func (g *RogueGame) packChar() byte { p := &g.Player for i := range p.PackUsed { if !p.PackUsed[i] { p.PackUsed[i] = true return byte(i) + 'a' } } return byte(len(p.PackUsed)) + 'a' // C would walk off the array here } // 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 kind != KindNone && kind != item.Kind && !(kind == KindCallable && item.Kind != KindFood && item.Kind != KindAmulet) && !(kind == KindRingOrStick && (item.Kind == KindRing || item.Kind == KindWand)) { continue } g.NObjs++ g.Msgs.MsgEsc = true line := string(item.PackCh) + ") " + g.invName(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 g.Options.Terse { if kind == KindNone { g.msg("empty handed") } else { g.msg("nothing appropriate") } } else { if kind == KindNone { g.msg("you are empty handed") } else { g.msg("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.findObj(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) detachObj(&g.Level.Objects, obj) p.Room.GoldVal = 0 default: g.addPack(nil, false) } } // 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.addmsg("you ") } g.msg("moved onto %s", g.invName(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.invName(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.invName(obj, false)) return } } g.msg("'%s' not in pack", unctrl(mch)) } // getItem picks something out of a pack for a purpose (pack.c get_item). func (g *RogueGame) getItem(purpose string, kind ObjectKind) *Object { p := &g.Player if len(p.Pack) == 0 { g.msg("you aren't carrying anything") return nil } if g.Again { if g.LastPick != nil { return g.LastPick } g.msg("you ran out") return nil } for { if !g.Options.Terse { g.addmsg("which object do you want to ") } g.addmsg("%s", purpose) if g.Options.Terse { g.addmsg(" what") } g.msg("? (* for list): ") 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 } 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 } continue } for _, obj := range p.Pack { if obj.PackCh == ch { return obj } } g.msg("'%s' is not a valid item", unctrl(ch)) } } // 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.addmsg("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 }