mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-11-19 10:56:07 -05:00
Initial inventory handling (#270)
Add struct to display inventory panel. Add struct to handle inventory and merchant grids. Hook up `i` key to toggle inventory panel.
This commit is contained in:
parent
c01bedaedf
commit
d6769975cd
@ -1,13 +1,13 @@
|
||||
package d2core
|
||||
|
||||
type CharacterEquipment struct {
|
||||
Head InventoryItemArmor // Head
|
||||
Torso InventoryItemArmor // TR
|
||||
Legs InventoryItemArmor // Legs
|
||||
RightArm InventoryItemArmor // RA
|
||||
LeftArm InventoryItemArmor // LA
|
||||
LeftHand InventoryItemWeapon // LH
|
||||
RightHand InventoryItemWeapon // RH
|
||||
Shield InventoryItemArmor // SH
|
||||
Head *InventoryItemArmor // Head
|
||||
Torso *InventoryItemArmor // TR
|
||||
Legs *InventoryItemArmor // Legs
|
||||
RightArm *InventoryItemArmor // RA
|
||||
LeftArm *InventoryItemArmor // LA
|
||||
LeftHand *InventoryItemWeapon // LH
|
||||
RightHand *InventoryItemWeapon // RH
|
||||
Shield *InventoryItemArmor // SH
|
||||
// S1-S8?
|
||||
}
|
||||
|
@ -99,5 +99,5 @@ func (v *Game) Advance(tickTime float64) {
|
||||
rx, ry := v.mapEngine.WorldToOrtho(v.hero.AnimatedEntity.LocationX/5, v.hero.AnimatedEntity.LocationY/5)
|
||||
v.mapEngine.MoveCameraTo(rx, ry)
|
||||
|
||||
v.gameControls.Move(tickTime)
|
||||
v.gameControls.Update(tickTime)
|
||||
}
|
||||
|
@ -20,16 +20,16 @@ func CreateHero(x, y int32, direction int, heroType d2enum.Hero, equipment Chara
|
||||
Mode: d2enum.AnimationModePlayerNeutral.String(),
|
||||
Base: "/data/global/chars",
|
||||
Token: heroType.GetToken(),
|
||||
Class: equipment.RightHand.GetWeaponClass(),
|
||||
SH: equipment.Shield.GetItemCode(),
|
||||
Class: equipment.RightHand.WeaponClass(),
|
||||
SH: equipment.Shield.ItemCode(),
|
||||
// TODO: Offhand class?
|
||||
HD: equipment.Head.GetArmorClass(),
|
||||
TR: equipment.Torso.GetArmorClass(),
|
||||
LG: equipment.Legs.GetArmorClass(),
|
||||
RA: equipment.RightArm.GetArmorClass(),
|
||||
LA: equipment.LeftArm.GetArmorClass(),
|
||||
RH: equipment.RightHand.GetItemCode(),
|
||||
LH: equipment.LeftHand.GetItemCode(),
|
||||
HD: equipment.Head.ArmorClass(),
|
||||
TR: equipment.Torso.ArmorClass(),
|
||||
LG: equipment.Legs.ArmorClass(),
|
||||
RA: equipment.RightArm.ArmorClass(),
|
||||
LA: equipment.LeftArm.ArmorClass(),
|
||||
RH: equipment.RightHand.ItemCode(),
|
||||
LH: equipment.LeftHand.ItemCode(),
|
||||
}
|
||||
|
||||
entity, err := d2render.CreateAnimatedEntity(x, y, object, d2resource.PaletteUnits)
|
||||
@ -38,7 +38,7 @@ func CreateHero(x, y int32, direction int, heroType d2enum.Hero, equipment Chara
|
||||
}
|
||||
|
||||
result := &Hero{AnimatedEntity: entity, Equipment: equipment, mode: d2enum.AnimationModePlayerTownNeutral, direction: direction}
|
||||
result.AnimatedEntity.SetMode(result.mode.String(), equipment.RightHand.GetWeaponClass(), direction)
|
||||
result.AnimatedEntity.SetMode(result.mode.String(), equipment.RightHand.WeaponClass(), direction)
|
||||
return result
|
||||
}
|
||||
|
||||
|
@ -10,17 +10,19 @@ import (
|
||||
type InventoryItemArmor struct {
|
||||
inventorySizeX int
|
||||
inventorySizeY int
|
||||
inventorySlotX int
|
||||
inventorySlotY int
|
||||
itemName string
|
||||
itemCode string
|
||||
armorClass string
|
||||
}
|
||||
|
||||
func GetArmorItemByCode(code string) InventoryItemArmor {
|
||||
func GetArmorItemByCode(code string) *InventoryItemArmor {
|
||||
result := d2datadict.Armors[code]
|
||||
if result == nil {
|
||||
log.Fatalf("Could not find armor entry for code '%s'", code)
|
||||
}
|
||||
return InventoryItemArmor{
|
||||
return &InventoryItemArmor{
|
||||
inventorySizeX: result.InventoryWidth,
|
||||
inventorySizeY: result.InventoryHeight,
|
||||
itemName: result.Name,
|
||||
@ -29,29 +31,45 @@ func GetArmorItemByCode(code string) InventoryItemArmor {
|
||||
}
|
||||
}
|
||||
|
||||
func (v InventoryItemArmor) GetArmorClass() string {
|
||||
if v.itemCode == "" {
|
||||
func (v *InventoryItemArmor) ArmorClass() string {
|
||||
if v == nil || v.itemCode == "" {
|
||||
return "lit"
|
||||
}
|
||||
return v.armorClass
|
||||
}
|
||||
|
||||
func (v InventoryItemArmor) GetInventoryItemName() string {
|
||||
func (v *InventoryItemArmor) InventoryItemName() string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return v.itemName
|
||||
}
|
||||
|
||||
func (v InventoryItemArmor) GetInventoryItemType() d2enum.InventoryItemType {
|
||||
func (v *InventoryItemArmor) InventoryItemType() d2enum.InventoryItemType {
|
||||
return d2enum.InventoryItemTypeArmor
|
||||
}
|
||||
|
||||
func (v InventoryItemArmor) GetInventoryGridSize() (int, int) {
|
||||
func (v *InventoryItemArmor) InventoryGridSize() (int, int) {
|
||||
return v.inventorySizeX, v.inventorySizeY
|
||||
}
|
||||
|
||||
func (v InventoryItemArmor) Serialize() []byte {
|
||||
func (v *InventoryItemArmor) InventoryGridSlot() (int, int) {
|
||||
return v.inventorySlotX, v.inventorySlotY
|
||||
}
|
||||
|
||||
func (v *InventoryItemArmor) SetInventoryGridSlot(x int, y int) {
|
||||
v.inventorySlotX, v.inventorySlotY = x, y
|
||||
}
|
||||
|
||||
func (v *InventoryItemArmor) Serialize() []byte {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
func (v InventoryItemArmor) GetItemCode() string {
|
||||
func (v *InventoryItemArmor) ItemCode() string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return v.itemCode
|
||||
}
|
||||
|
@ -10,19 +10,21 @@ import (
|
||||
type InventoryItemWeapon struct {
|
||||
inventorySizeX int
|
||||
inventorySizeY int
|
||||
inventorySlotX int
|
||||
inventorySlotY int
|
||||
itemName string
|
||||
itemCode string
|
||||
weaponClass string
|
||||
weaponClassOffHand string
|
||||
}
|
||||
|
||||
func GetWeaponItemByCode(code string) InventoryItemWeapon {
|
||||
func GetWeaponItemByCode(code string) *InventoryItemWeapon {
|
||||
// TODO: Non-normal codes will fail here...
|
||||
result := d2datadict.Weapons[code]
|
||||
if result == nil {
|
||||
log.Fatalf("Could not find weapon entry for code '%s'", code)
|
||||
}
|
||||
return InventoryItemWeapon{
|
||||
return &InventoryItemWeapon{
|
||||
inventorySizeX: result.InventoryWidth,
|
||||
inventorySizeY: result.InventoryHeight,
|
||||
itemName: result.Name,
|
||||
@ -32,36 +34,50 @@ func GetWeaponItemByCode(code string) InventoryItemWeapon {
|
||||
}
|
||||
}
|
||||
|
||||
func (v InventoryItemWeapon) GetWeaponClass() string {
|
||||
if v.itemCode == "" {
|
||||
func (v *InventoryItemWeapon) WeaponClass() string {
|
||||
if v == nil || v.itemCode == "" {
|
||||
return "hth"
|
||||
}
|
||||
return v.weaponClass
|
||||
}
|
||||
|
||||
func (v InventoryItemWeapon) GetWeaponClassOffHand() string {
|
||||
if v.itemCode == "" {
|
||||
func (v *InventoryItemWeapon) WeaponClassOffHand() string {
|
||||
if v == nil || v.itemCode == "" {
|
||||
return ""
|
||||
}
|
||||
return v.weaponClassOffHand
|
||||
}
|
||||
|
||||
func (v InventoryItemWeapon) GetInventoryItemName() string {
|
||||
func (v *InventoryItemWeapon) InventoryItemName() string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
return v.itemName
|
||||
}
|
||||
|
||||
func (v InventoryItemWeapon) GetInventoryItemType() d2enum.InventoryItemType {
|
||||
func (v *InventoryItemWeapon) InventoryItemType() d2enum.InventoryItemType {
|
||||
return d2enum.InventoryItemTypeWeapon
|
||||
}
|
||||
|
||||
func (v InventoryItemWeapon) GetInventoryGridSize() (int, int) {
|
||||
func (v *InventoryItemWeapon) InventoryGridSize() (int, int) {
|
||||
return v.inventorySizeX, v.inventorySizeY
|
||||
}
|
||||
|
||||
func (v InventoryItemWeapon) Serialize() []byte {
|
||||
func (v *InventoryItemWeapon) InventoryGridSlot() (int, int) {
|
||||
return v.inventorySlotX, v.inventorySlotY
|
||||
}
|
||||
|
||||
func (v *InventoryItemWeapon) SetInventoryGridSlot(x int, y int) {
|
||||
v.inventorySlotX, v.inventorySlotY = x, y
|
||||
}
|
||||
|
||||
func (v *InventoryItemWeapon) Serialize() []byte {
|
||||
return []byte{}
|
||||
}
|
||||
|
||||
func (v InventoryItemWeapon) GetItemCode() string {
|
||||
func (v *InventoryItemWeapon) ItemCode() string {
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
return v.itemCode
|
||||
}
|
@ -7,11 +7,20 @@ import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2mapengine"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
|
||||
"github.com/hajimehoshi/ebiten"
|
||||
"github.com/hajimehoshi/ebiten/inpututil"
|
||||
)
|
||||
|
||||
type Panel interface {
|
||||
IsOpen() bool
|
||||
Toggle()
|
||||
Open()
|
||||
Close()
|
||||
}
|
||||
|
||||
type GameControls struct {
|
||||
hero *d2core.Hero
|
||||
mapEngine *d2mapengine.MapEngine
|
||||
inventory *Inventory
|
||||
|
||||
// UI
|
||||
globeSprite *d2render.Sprite
|
||||
@ -24,10 +33,11 @@ func NewGameControls(hero *d2core.Hero, mapEngine *d2mapengine.MapEngine) *GameC
|
||||
return &GameControls{
|
||||
hero: hero,
|
||||
mapEngine: mapEngine,
|
||||
inventory: NewInventory(),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GameControls) Move(tickTime float64) {
|
||||
func (g *GameControls) Update(tickTime float64) {
|
||||
|
||||
if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) {
|
||||
px, py := g.mapEngine.ScreenToWorld(ebiten.CursorPosition())
|
||||
@ -58,6 +68,10 @@ func (g *GameControls) Move(tickTime float64) {
|
||||
g.hero.AnimatedEntity.SetTarget(g.hero.AnimatedEntity.LocationX+moveX, g.hero.AnimatedEntity.LocationY+moveY, 1)
|
||||
}
|
||||
|
||||
if inpututil.IsKeyJustPressed(ebiten.KeyI) {
|
||||
g.inventory.Toggle()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (g *GameControls) Load() {
|
||||
@ -65,43 +79,46 @@ func (g *GameControls) Load() {
|
||||
g.mainPanel, _ = d2render.LoadSprite(d2resource.GamePanels, d2resource.PaletteSky)
|
||||
g.menuButton, _ = d2render.LoadSprite(d2resource.MenuButton, d2resource.PaletteSky)
|
||||
g.skillIcon, _ = d2render.LoadSprite(d2resource.GenericSkills, d2resource.PaletteSky)
|
||||
g.inventory.Load()
|
||||
}
|
||||
|
||||
// TODO: consider caching the panels to single image that is reused.
|
||||
func (g *GameControls) Render(target *d2surface.Surface) {
|
||||
g.inventory.Render(target)
|
||||
|
||||
width, height := target.GetSize()
|
||||
offset := int(0)
|
||||
|
||||
// Left globe holder
|
||||
g.mainPanel.SetCurrentFrame(0)
|
||||
w, _ := g.mainPanel.GetCurrentFrameSize()
|
||||
g.mainPanel.SetPosition(int(offset), height)
|
||||
g.mainPanel.SetPosition(offset, height)
|
||||
g.mainPanel.Render(target)
|
||||
|
||||
// Left globe
|
||||
g.globeSprite.SetCurrentFrame(0)
|
||||
g.globeSprite.SetPosition(int(offset+28), height-5)
|
||||
g.globeSprite.SetPosition(offset+28, height-5)
|
||||
g.globeSprite.Render(target)
|
||||
offset += w
|
||||
|
||||
// Left skill
|
||||
g.skillIcon.SetCurrentFrame(2)
|
||||
w, _ = g.skillIcon.GetCurrentFrameSize()
|
||||
g.skillIcon.SetPosition(int(offset), height)
|
||||
g.skillIcon.SetPosition(offset, height)
|
||||
g.skillIcon.Render(target)
|
||||
offset += w
|
||||
|
||||
// Left skill selector
|
||||
g.mainPanel.SetCurrentFrame(1)
|
||||
w, _ = g.mainPanel.GetCurrentFrameSize()
|
||||
g.mainPanel.SetPosition(int(offset), height)
|
||||
g.mainPanel.SetPosition(offset, height)
|
||||
g.mainPanel.Render(target)
|
||||
offset += w
|
||||
|
||||
// Stamina
|
||||
g.mainPanel.SetCurrentFrame(2)
|
||||
w, _ = g.mainPanel.GetCurrentFrameSize()
|
||||
g.mainPanel.SetPosition(int(offset), height)
|
||||
g.mainPanel.SetPosition(offset, height)
|
||||
g.mainPanel.Render(target)
|
||||
offset += w
|
||||
|
||||
@ -114,33 +131,33 @@ func (g *GameControls) Render(target *d2surface.Surface) {
|
||||
// Potions
|
||||
g.mainPanel.SetCurrentFrame(3)
|
||||
w, _ = g.mainPanel.GetCurrentFrameSize()
|
||||
g.mainPanel.SetPosition(int(offset), height)
|
||||
g.mainPanel.SetPosition(offset, height)
|
||||
g.mainPanel.Render(target)
|
||||
offset += w
|
||||
|
||||
// Right skill selector
|
||||
g.mainPanel.SetCurrentFrame(4)
|
||||
w, _ = g.mainPanel.GetCurrentFrameSize()
|
||||
g.mainPanel.SetPosition(int(offset), height)
|
||||
g.mainPanel.SetPosition(offset, height)
|
||||
g.mainPanel.Render(target)
|
||||
offset += w
|
||||
|
||||
// Right skill
|
||||
g.skillIcon.SetCurrentFrame(10)
|
||||
w, _ = g.skillIcon.GetCurrentFrameSize()
|
||||
g.skillIcon.SetPosition(int(offset), height)
|
||||
g.skillIcon.SetPosition(offset, height)
|
||||
g.skillIcon.Render(target)
|
||||
offset += w
|
||||
|
||||
// Right globe holder
|
||||
g.mainPanel.SetCurrentFrame(5)
|
||||
w, _ = g.mainPanel.GetCurrentFrameSize()
|
||||
g.mainPanel.SetPosition(int(offset), height)
|
||||
g.mainPanel.SetPosition(offset, height)
|
||||
g.mainPanel.Render(target)
|
||||
|
||||
// Right globe
|
||||
g.globeSprite.SetCurrentFrame(1)
|
||||
g.globeSprite.SetPosition(int(offset)+8, height-8)
|
||||
g.globeSprite.SetPosition(offset+8, height-8)
|
||||
g.globeSprite.Render(target)
|
||||
|
||||
}
|
||||
|
133
d2player/inventory.go
Normal file
133
d2player/inventory.go
Normal file
@ -0,0 +1,133 @@
|
||||
package d2player
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
|
||||
)
|
||||
|
||||
type Inventory struct {
|
||||
frame *d2render.Sprite
|
||||
panel *d2render.Sprite
|
||||
grid *ItemGrid
|
||||
originX int
|
||||
originY int
|
||||
isOpen bool
|
||||
}
|
||||
|
||||
func NewInventory() *Inventory {
|
||||
originX := 400
|
||||
originY := 0
|
||||
return &Inventory{
|
||||
grid: NewItemGrid(10, 4, originX+19, originY+320),
|
||||
originX: originX,
|
||||
originY: originY,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *Inventory) IsOpen() bool {
|
||||
return g.isOpen
|
||||
}
|
||||
|
||||
func (g *Inventory) Toggle() {
|
||||
g.isOpen = !g.isOpen
|
||||
}
|
||||
|
||||
func (g *Inventory) Open() {
|
||||
g.isOpen = true
|
||||
}
|
||||
|
||||
func (g *Inventory) Close() {
|
||||
g.isOpen = false
|
||||
}
|
||||
|
||||
func (g *Inventory) Load() {
|
||||
g.frame, _ = d2render.LoadSprite(d2resource.Frame, d2resource.PaletteSky)
|
||||
g.panel, _ = d2render.LoadSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
|
||||
|
||||
items := []InventoryItem{
|
||||
d2core.GetWeaponItemByCode("wnd"),
|
||||
d2core.GetWeaponItemByCode("sst"),
|
||||
d2core.GetWeaponItemByCode("jav"),
|
||||
d2core.GetArmorItemByCode("buc"),
|
||||
d2core.GetWeaponItemByCode("clb"),
|
||||
}
|
||||
g.grid.Add(items...)
|
||||
}
|
||||
|
||||
func (g *Inventory) Render(target *d2surface.Surface) {
|
||||
if !g.isOpen {
|
||||
return
|
||||
}
|
||||
|
||||
x, y := g.originX, g.originY
|
||||
|
||||
// Frame
|
||||
// Top left
|
||||
g.frame.SetCurrentFrame(5)
|
||||
w, h := g.frame.GetCurrentFrameSize()
|
||||
g.frame.SetPosition(x, y+h)
|
||||
g.frame.Render(target)
|
||||
x += w
|
||||
|
||||
// Top right
|
||||
g.frame.SetCurrentFrame(6)
|
||||
w, h = g.frame.GetCurrentFrameSize()
|
||||
g.frame.SetPosition(x, y+h)
|
||||
g.frame.Render(target)
|
||||
x += w
|
||||
y += h
|
||||
|
||||
// Right
|
||||
g.frame.SetCurrentFrame(7)
|
||||
w, h = g.frame.GetCurrentFrameSize()
|
||||
g.frame.SetPosition(x-w, y+h)
|
||||
g.frame.Render(target)
|
||||
y += h
|
||||
|
||||
// Bottom right
|
||||
g.frame.SetCurrentFrame(8)
|
||||
w, h = g.frame.GetCurrentFrameSize()
|
||||
g.frame.SetPosition(x-w, y+h)
|
||||
g.frame.Render(target)
|
||||
x -= w
|
||||
|
||||
// Bottom left
|
||||
g.frame.SetCurrentFrame(9)
|
||||
w, h = g.frame.GetCurrentFrameSize()
|
||||
g.frame.SetPosition(x-w, y+h)
|
||||
g.frame.Render(target)
|
||||
|
||||
x, y = g.originX, g.originY
|
||||
y += 64
|
||||
|
||||
// Panel
|
||||
// Top left
|
||||
g.panel.SetCurrentFrame(4)
|
||||
w, h = g.panel.GetCurrentFrameSize()
|
||||
g.panel.SetPosition(x, y+h)
|
||||
g.panel.Render(target)
|
||||
x += w
|
||||
|
||||
// Top right
|
||||
g.panel.SetCurrentFrame(5)
|
||||
w, h = g.panel.GetCurrentFrameSize()
|
||||
g.panel.SetPosition(x, y+h)
|
||||
g.panel.Render(target)
|
||||
y += h
|
||||
|
||||
// Bottom right
|
||||
g.panel.SetCurrentFrame(7)
|
||||
w, h = g.panel.GetCurrentFrameSize()
|
||||
g.panel.SetPosition(x, y+h)
|
||||
g.panel.Render(target)
|
||||
|
||||
// Bottom left
|
||||
g.panel.SetCurrentFrame(6)
|
||||
w, h = g.panel.GetCurrentFrameSize()
|
||||
g.panel.SetPosition(x-w, y+h)
|
||||
g.panel.Render(target)
|
||||
|
||||
g.grid.Render(target)
|
||||
}
|
202
d2player/inventory_grid.go
Normal file
202
d2player/inventory_grid.go
Normal file
@ -0,0 +1,202 @@
|
||||
package d2player
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/OpenDiablo2/D2Shared/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface"
|
||||
"log"
|
||||
)
|
||||
|
||||
type InventoryItem interface {
|
||||
InventoryGridSize() (width int, height int)
|
||||
ItemCode() string
|
||||
InventoryGridSlot() (x int, y int)
|
||||
SetInventoryGridSlot(x int, y int)
|
||||
}
|
||||
|
||||
var ErrorInventoryFull = errors.New("inventory full")
|
||||
|
||||
// Reusable grid for use with player and merchant inventory.
|
||||
// Handles layout and rendering item icons based on code.
|
||||
type ItemGrid struct {
|
||||
items []InventoryItem
|
||||
width int
|
||||
height int
|
||||
originX int
|
||||
originY int
|
||||
sprites map[string]*d2render.Sprite
|
||||
slotSize int
|
||||
}
|
||||
|
||||
func NewItemGrid(width int, height int, originX int, originY int) *ItemGrid {
|
||||
return &ItemGrid{
|
||||
width: width,
|
||||
height: height,
|
||||
originX: originX,
|
||||
originY: originY,
|
||||
slotSize: 29,
|
||||
sprites: make(map[string]*d2render.Sprite),
|
||||
}
|
||||
}
|
||||
|
||||
func (g *ItemGrid) SlotToScreen(slotX int, slotY int) (screenX int, screenY int) {
|
||||
screenX = g.originX + slotX*g.slotSize
|
||||
screenY = g.originY + slotY*g.slotSize
|
||||
return screenX, screenY
|
||||
}
|
||||
|
||||
func (g *ItemGrid) ScreenToSlot(screenX int, screenY int) (slotX int, slotY int) {
|
||||
slotX = (screenX - g.originX) / g.slotSize
|
||||
slotY = (screenY - g.originY) / g.slotSize
|
||||
return slotX, slotY
|
||||
}
|
||||
|
||||
func (g *ItemGrid) GetSlot(x int, y int) InventoryItem {
|
||||
for _, item := range g.items {
|
||||
slotX, slotY := item.InventoryGridSlot()
|
||||
width, height := item.InventoryGridSize()
|
||||
|
||||
if x >= slotX && x < slotX+width && y >= slotY && y < slotY+height {
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add places a given set of items into the first available slots.
|
||||
// Returns a count of the number of items which could be inserted.
|
||||
func (g *ItemGrid) Add(items ...InventoryItem) (int, error) {
|
||||
added := 0
|
||||
var err error
|
||||
|
||||
for _, item := range items {
|
||||
if g.add(item) {
|
||||
added++
|
||||
} else {
|
||||
err = ErrorInventoryFull
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
g.Load(items...)
|
||||
|
||||
return added, err
|
||||
}
|
||||
|
||||
// Load reads the inventory sprites for items into local cache for rendering.
|
||||
func (g *ItemGrid) Load(items ...InventoryItem) {
|
||||
var itemSprite *d2render.Sprite
|
||||
var err error
|
||||
|
||||
for _, item := range items {
|
||||
if _, exists := g.sprites[item.ItemCode()]; exists {
|
||||
// Already loaded, don't reload.
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: Put the pattern into D2Shared
|
||||
itemSprite, err = d2render.LoadSprite(
|
||||
fmt.Sprintf("/data/global/items/inv%s.dc6", item.ItemCode()),
|
||||
d2resource.PaletteSky,
|
||||
)
|
||||
if err != nil {
|
||||
log.Printf("failed to load sprite for item (%s): %v", item.ItemCode(), err)
|
||||
continue
|
||||
}
|
||||
|
||||
g.sprites[item.ItemCode()] = itemSprite
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Walk from top left to bottom right until a position large enough to hold the item is found.
|
||||
// This is inefficient but simplifies the storage. At most a hundred or so cells will be looped, so impact is minimal.
|
||||
func (g *ItemGrid) add(item InventoryItem) bool {
|
||||
for y := 0; y < g.height; y++ {
|
||||
for x := 0; x < g.width; x++ {
|
||||
if !g.canFit(x, y, item) {
|
||||
continue
|
||||
}
|
||||
|
||||
g.set(x, y, item)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// canFit loops over all items to determine if any other items would overlap the given position.
|
||||
func (g *ItemGrid) canFit(x int, y int, item InventoryItem) bool {
|
||||
insertWidth, insertHeight := item.InventoryGridSize()
|
||||
if x+insertWidth > g.width || y+insertHeight > g.height {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, compItem := range g.items {
|
||||
slotX, slotY := compItem.InventoryGridSlot()
|
||||
compWidth, compHeight := compItem.InventoryGridSize()
|
||||
|
||||
if x+insertWidth >= slotX &&
|
||||
x < slotX+compWidth &&
|
||||
y+insertHeight >= slotY &&
|
||||
y < slotY+compHeight {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (g *ItemGrid) Set(x int, y int, item InventoryItem) error {
|
||||
if !g.canFit(x, y, item) {
|
||||
return fmt.Errorf("can not set item (%s) to position (%v, %v)", item.ItemCode(), x, y)
|
||||
}
|
||||
g.set(x, y, item)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *ItemGrid) set(x int, y int, item InventoryItem) {
|
||||
item.SetInventoryGridSlot(x, y)
|
||||
g.items = append(g.items, item)
|
||||
|
||||
g.Load(item)
|
||||
}
|
||||
|
||||
// Remove does an in place filter to remove the element from the slice of items.
|
||||
func (g *ItemGrid) Remove(item InventoryItem) {
|
||||
n := 0
|
||||
for _, compItem := range g.items {
|
||||
if compItem == item {
|
||||
continue
|
||||
}
|
||||
g.items[n] = compItem
|
||||
n++
|
||||
}
|
||||
|
||||
g.items = g.items[:n]
|
||||
}
|
||||
|
||||
func (g *ItemGrid) Render(target *d2surface.Surface) {
|
||||
for _, item := range g.items {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
itemSprite := g.sprites[item.ItemCode()]
|
||||
if itemSprite == nil {
|
||||
// In case it failed to load.
|
||||
// TODO: fallback to something
|
||||
continue
|
||||
}
|
||||
|
||||
slotX, slotY := g.SlotToScreen(item.InventoryGridSlot())
|
||||
_, h := itemSprite.GetCurrentFrameSize()
|
||||
itemSprite.SetPosition(slotX, slotY+h)
|
||||
_ = itemSprite.Render(target)
|
||||
|
||||
}
|
||||
}
|
111
d2player/inventory_grid_test.go
Normal file
111
d2player/inventory_grid_test.go
Normal file
@ -0,0 +1,111 @@
|
||||
package d2player
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type TestItem struct {
|
||||
width int
|
||||
height int
|
||||
inventorySlotX int
|
||||
inventorySlotY int
|
||||
}
|
||||
|
||||
func (t *TestItem) InventoryGridSize() (int, int) {
|
||||
return t.width, t.height
|
||||
}
|
||||
|
||||
func (t *TestItem) ItemCode() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (t *TestItem) InventoryGridSlot() (int, int) {
|
||||
return t.inventorySlotX, t.inventorySlotY
|
||||
}
|
||||
|
||||
func (t *TestItem) SetInventoryGridSlot(x int, y int) {
|
||||
t.inventorySlotX, t.inventorySlotY = x, y
|
||||
}
|
||||
|
||||
func NewTestItem(width int, height int) *TestItem {
|
||||
return &TestItem{width: width, height: height}
|
||||
}
|
||||
|
||||
func TestItemGrid_Add_Basic(t *testing.T) {
|
||||
grid := NewItemGrid(2, 2, 0, 0)
|
||||
|
||||
tl := NewTestItem(1, 1)
|
||||
tr := NewTestItem(1, 1)
|
||||
bl := NewTestItem(1, 1)
|
||||
br := NewTestItem(1, 1)
|
||||
|
||||
added, err := grid.Add(tl, tr, bl, br)
|
||||
|
||||
assert.Equal(t, 4, added)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tl, grid.GetSlot(0, 0))
|
||||
assert.Equal(t, tr, grid.GetSlot(1, 0))
|
||||
assert.Equal(t, bl, grid.GetSlot(0, 1))
|
||||
assert.Equal(t, br, grid.GetSlot(1, 1))
|
||||
|
||||
}
|
||||
|
||||
func TestItemGrid_Add_OverflowBasic(t *testing.T) {
|
||||
grid := NewItemGrid(2, 2, 0, 0)
|
||||
|
||||
tl := NewTestItem(1, 1)
|
||||
tr := NewTestItem(1, 1)
|
||||
bl := NewTestItem(1, 1)
|
||||
br := NewTestItem(1, 1)
|
||||
o := NewTestItem(1, 1)
|
||||
|
||||
added, err := grid.Add(tl, tr, bl, br, o)
|
||||
|
||||
assert.Equal(t, 4, added)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tl, grid.GetSlot(0, 0))
|
||||
assert.Equal(t, tr, grid.GetSlot(1, 0))
|
||||
assert.Equal(t, bl, grid.GetSlot(0, 1))
|
||||
assert.Equal(t, br, grid.GetSlot(1, 1))
|
||||
|
||||
}
|
||||
|
||||
func TestItemGrid_Add_LargeItem(t *testing.T) {
|
||||
grid := NewItemGrid(3, 3, 0, 0)
|
||||
|
||||
tl := NewTestItem(1, 1)
|
||||
o := NewTestItem(2, 2)
|
||||
|
||||
added, err := grid.Add(tl, o)
|
||||
|
||||
assert.Equal(t, 2, added)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tl, grid.GetSlot(0, 0))
|
||||
assert.Equal(t, o, grid.GetSlot(1, 0))
|
||||
assert.Equal(t, o, grid.GetSlot(2, 0))
|
||||
assert.Nil(t, grid.GetSlot(0, 1))
|
||||
assert.Equal(t, o, grid.GetSlot(1, 1))
|
||||
assert.Equal(t, o, grid.GetSlot(2, 1))
|
||||
assert.Nil(t, grid.GetSlot(0, 2))
|
||||
assert.Nil(t, grid.GetSlot(1, 2))
|
||||
assert.Nil(t, grid.GetSlot(2, 2))
|
||||
|
||||
}
|
||||
|
||||
func TestItemGrid_Add_OverflowLargeItem(t *testing.T) {
|
||||
grid := NewItemGrid(2, 2, 0, 0)
|
||||
|
||||
tl := NewTestItem(1, 1)
|
||||
o := NewTestItem(2, 2)
|
||||
|
||||
added, err := grid.Add(tl, o)
|
||||
|
||||
assert.Equal(t, 1, added)
|
||||
assert.Error(t, err)
|
||||
assert.Equal(t, tl, grid.GetSlot(0, 0))
|
||||
assert.Nil(t, grid.GetSlot(1, 0))
|
||||
assert.Nil(t, grid.GetSlot(0, 1))
|
||||
assert.Nil(t, grid.GetSlot(1, 1))
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user