2019-12-28 23:32:53 -05:00
|
|
|
package d2player
|
|
|
|
|
|
|
|
import (
|
2020-08-11 18:01:33 -04:00
|
|
|
"fmt"
|
2020-08-03 13:44:00 -04:00
|
|
|
|
2020-10-25 03:42:31 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
2020-09-20 17:52:01 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
|
|
|
|
|
2020-06-23 14:12:30 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
2020-06-29 00:41:58 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
2020-01-26 00:39:13 -05:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
2020-02-01 18:55:56 -05:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
2020-08-03 13:44:00 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2item/diablo2item"
|
2020-02-01 18:55:56 -05:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
2019-12-28 23:32:53 -05:00
|
|
|
)
|
|
|
|
|
2020-10-25 03:42:31 -04:00
|
|
|
const (
|
|
|
|
frameInventoryTopLeft = 4
|
|
|
|
frameInventoryTopRight = 5
|
|
|
|
frameInventoryBottomLeft = 6
|
|
|
|
frameInventoryBottomRight = 7
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
blackAlpha70 = 0x000000C8
|
|
|
|
)
|
|
|
|
|
2020-08-11 18:01:33 -04:00
|
|
|
// Inventory represents the inventory
|
2019-12-28 23:32:53 -05:00
|
|
|
type Inventory struct {
|
2020-09-12 16:51:30 -04:00
|
|
|
asset *d2asset.AssetManager
|
2020-09-20 17:52:01 -04:00
|
|
|
item *diablo2item.ItemFactory
|
2020-08-11 18:01:33 -04:00
|
|
|
uiManager *d2ui.UIManager
|
2020-10-22 12:54:45 -04:00
|
|
|
frame *d2ui.UIFrame
|
2020-08-11 18:01:33 -04:00
|
|
|
panel *d2ui.Sprite
|
|
|
|
grid *ItemGrid
|
|
|
|
hoverLabel *d2ui.Label
|
|
|
|
hoverX int
|
|
|
|
hoverY int
|
|
|
|
originX int
|
|
|
|
originY int
|
|
|
|
lastMouseX int
|
|
|
|
lastMouseY int
|
|
|
|
hovering bool
|
|
|
|
isOpen bool
|
2019-12-28 23:32:53 -05:00
|
|
|
}
|
|
|
|
|
2020-08-11 18:01:33 -04:00
|
|
|
// NewInventory creates an inventory instance and returns a pointer to it
|
2020-09-12 16:51:30 -04:00
|
|
|
func NewInventory(asset *d2asset.AssetManager, ui *d2ui.UIManager,
|
2020-09-20 17:52:01 -04:00
|
|
|
record *d2records.InventoryRecord) *Inventory {
|
2020-08-06 10:30:23 -04:00
|
|
|
hoverLabel := ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteStatic)
|
2020-08-03 13:44:00 -04:00
|
|
|
hoverLabel.Alignment = d2gui.HorizontalAlignCenter
|
|
|
|
|
2020-10-25 10:21:14 -04:00
|
|
|
// https://github.com/OpenDiablo2/OpenDiablo2/issues/797
|
|
|
|
itemFactory, _ := diablo2item.NewItemFactory(asset)
|
2020-09-20 17:52:01 -04:00
|
|
|
|
2019-12-28 23:32:53 -05:00
|
|
|
return &Inventory{
|
2020-09-12 16:51:30 -04:00
|
|
|
asset: asset,
|
2020-08-06 10:30:23 -04:00
|
|
|
uiManager: ui,
|
2020-09-20 17:52:01 -04:00
|
|
|
item: itemFactory,
|
2020-09-12 16:51:30 -04:00
|
|
|
grid: NewItemGrid(asset, ui, record),
|
2020-08-03 13:44:00 -04:00
|
|
|
originX: record.Panel.Left,
|
2020-08-06 10:30:23 -04:00
|
|
|
hoverLabel: hoverLabel,
|
2020-07-11 11:25:34 -04:00
|
|
|
// originY: record.Panel.Top,
|
|
|
|
originY: 0, // expansion data has these all offset by +60 ...
|
2019-12-28 23:32:53 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-11 18:01:33 -04:00
|
|
|
// IsOpen returns true if the inventory is open
|
2019-12-28 23:32:53 -05:00
|
|
|
func (g *Inventory) IsOpen() bool {
|
|
|
|
return g.isOpen
|
|
|
|
}
|
|
|
|
|
2020-08-11 18:01:33 -04:00
|
|
|
// Toggle negates the open state of the inventory
|
2019-12-28 23:32:53 -05:00
|
|
|
func (g *Inventory) Toggle() {
|
|
|
|
g.isOpen = !g.isOpen
|
|
|
|
}
|
|
|
|
|
2020-08-11 18:01:33 -04:00
|
|
|
// Open opens the inventory
|
2019-12-28 23:32:53 -05:00
|
|
|
func (g *Inventory) Open() {
|
|
|
|
g.isOpen = true
|
|
|
|
}
|
|
|
|
|
2020-08-11 18:01:33 -04:00
|
|
|
// Close closes the inventory
|
2019-12-28 23:32:53 -05:00
|
|
|
func (g *Inventory) Close() {
|
|
|
|
g.isOpen = false
|
|
|
|
}
|
|
|
|
|
2020-09-14 14:47:11 -04:00
|
|
|
// Load the resources required by the inventory
|
2019-12-28 23:32:53 -05:00
|
|
|
func (g *Inventory) Load() {
|
2020-10-22 12:54:45 -04:00
|
|
|
g.frame = d2ui.NewUIFrame(g.asset, g.uiManager, d2ui.FrameRight)
|
2020-01-31 23:18:11 -05:00
|
|
|
|
Decouple asset manager from renderer (#730)
* improve AssetManager implementation
Notable changes are:
* removed the individual managers inside of d2asset, only one asset manager
* AssetManager now has caches for the types of files it loads
* created a type for TextDictionary (the txt file structs)
* fixed a file path bug in d2loader Source
* fixed a asset stream bug in d2loader Asset
* d2loader.Loader now needs a d2config.Config on creation (for resolving locale files)
* updated the mpq file in d2asset test data, added test case for "sub-directory"
* added a Data method to d2asset.Asset. The data is cached on first full read.
* renamed ArchiveDataStream to DataStream in d2interface
* moved palette utility func out of d2asset and into d2util
* bugfix for MacOS mpq loader issue
* lint fixes, added data caching to filesystem asset
* adding comment for mpq asset close
* Decouple d2asset from d2render
Notable changes in d2common:
* d2dcc.Load now fully decodes the dcc and stores the directions/frames in the dcc struct
* un-exported dcc.decodeDirection, it is only used in d2dcc
* removed font interface from d2interface, we only have one font implementation
* added `Renderer` method to d2interface.Surface, animations use this to bind to a renderer and create surfaces as they need
* added `BindRenderer` method to animation interface
Notable changes in d2common/d2asset:
* **d2asset.NewAssetManager only needs to be passed a d2config.Config**, it is decoupled from d2render
* exported Animation
* Animation implementation binds to the renderer to create surfaces only on the first time it is rendered
* font, dcc, dc6 initialization logic moved out of asset_manager.go
* for dc6 and dcc animations, the process of decoding and creating render surfaces has been broken into different methods
* the d2asset.Font struct now stores font table data for initialization purposes
Notable changes in d2core/d2render:
* Surfaces store a renderer reference, this allows animations to bind to the renderer and create a surface just-in-time
**These last changes should have been a separate PR, sorry.**
Notable changes in d2core/d2ui:
* ui.NewSprite now handles creating an animation internally, only needs image and palette path as arguments
Notable Changes in d2game:
Because of the change in d2ui, all instances of this code pattern...
```golang
animation, err := screen.asset.LoadAnimation(imgPath, palettePath)
sprite, err := screen.ui.NewSprite(animation)
```
... becomes this ...
```golang
sprite, err := screen.ui.NewSprite(imgPath, palettePath)
```
2020-09-14 17:31:45 -04:00
|
|
|
g.panel, _ = g.uiManager.NewSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
|
2020-09-20 17:52:01 -04:00
|
|
|
|
2020-10-25 10:21:14 -04:00
|
|
|
// https://github.com/OpenDiablo2/OpenDiablo2/issues/795
|
2020-09-20 17:52:01 -04:00
|
|
|
testInventoryCodes := [][]string{
|
|
|
|
{"kit", "Crimson", "of the Bat", "of Frost"},
|
|
|
|
{"rin", "Steel", "of Shock"},
|
|
|
|
{"jav"},
|
|
|
|
{"buc"},
|
|
|
|
}
|
|
|
|
|
|
|
|
inventoryItems := make([]InventoryItem, 0)
|
|
|
|
|
|
|
|
for idx := range testInventoryCodes {
|
|
|
|
item, err := g.item.NewItem(testInventoryCodes[idx]...)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
item.Identify()
|
|
|
|
inventoryItems = append(inventoryItems, item)
|
|
|
|
}
|
|
|
|
|
2020-10-25 10:21:14 -04:00
|
|
|
// https://github.com/OpenDiablo2/OpenDiablo2/issues/795
|
2020-09-20 17:52:01 -04:00
|
|
|
testEquippedItemCodes := map[d2enum.EquippedSlot][]string{
|
|
|
|
d2enum.EquippedSlotLeftArm: {"wnd"},
|
|
|
|
d2enum.EquippedSlotRightArm: {"buc"},
|
|
|
|
d2enum.EquippedSlotHead: {"crn"},
|
|
|
|
d2enum.EquippedSlotTorso: {"plt"},
|
|
|
|
d2enum.EquippedSlotLegs: {"vbt"},
|
|
|
|
d2enum.EquippedSlotBelt: {"vbl"},
|
|
|
|
d2enum.EquippedSlotGloves: {"lgl"},
|
|
|
|
d2enum.EquippedSlotLeftHand: {"rin"},
|
|
|
|
d2enum.EquippedSlotRightHand: {"rin"},
|
|
|
|
d2enum.EquippedSlotNeck: {"amu"},
|
|
|
|
}
|
|
|
|
|
|
|
|
for slot := range testEquippedItemCodes {
|
|
|
|
item, err := g.item.NewItem(testEquippedItemCodes[slot]...)
|
|
|
|
if err != nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
g.grid.ChangeEquippedSlot(slot, item)
|
2019-12-28 23:32:53 -05:00
|
|
|
}
|
2020-08-11 18:01:33 -04:00
|
|
|
|
2020-10-22 12:54:45 -04:00
|
|
|
_, err := g.grid.Add(inventoryItems...)
|
2020-08-11 18:01:33 -04:00
|
|
|
if err != nil {
|
|
|
|
fmt.Printf("could not add items to the inventory, err: %v\n", err)
|
|
|
|
}
|
2019-12-28 23:32:53 -05:00
|
|
|
}
|
|
|
|
|
2020-08-11 18:01:33 -04:00
|
|
|
// Render draws the inventory onto the given surface
|
2020-07-26 14:52:54 -04:00
|
|
|
func (g *Inventory) Render(target d2interface.Surface) error {
|
2019-12-28 23:32:53 -05:00
|
|
|
if !g.isOpen {
|
2020-07-26 14:52:54 -04:00
|
|
|
return nil
|
2019-12-28 23:32:53 -05:00
|
|
|
}
|
|
|
|
|
2020-10-25 03:42:31 -04:00
|
|
|
if err := g.frame.Render(target); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-12-28 23:32:53 -05:00
|
|
|
|
2020-10-22 12:54:45 -04:00
|
|
|
x, y := g.originX+1, g.originY
|
2019-12-28 23:32:53 -05:00
|
|
|
y += 64
|
|
|
|
|
|
|
|
// Panel
|
|
|
|
// Top left
|
2020-10-25 03:42:31 -04:00
|
|
|
if err := g.panel.SetCurrentFrame(frameInventoryTopLeft); err != nil {
|
2020-07-26 14:52:54 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-10-22 12:54:45 -04:00
|
|
|
w, h := g.panel.GetCurrentFrameSize()
|
2020-07-26 14:52:54 -04:00
|
|
|
|
2019-12-28 23:32:53 -05:00
|
|
|
g.panel.SetPosition(x, y+h)
|
2020-07-26 14:52:54 -04:00
|
|
|
|
|
|
|
if err := g.panel.Render(target); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-12-28 23:32:53 -05:00
|
|
|
x += w
|
|
|
|
|
|
|
|
// Top right
|
2020-10-25 03:42:31 -04:00
|
|
|
if err := g.panel.SetCurrentFrame(frameInventoryTopRight); err != nil {
|
2020-07-26 14:52:54 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-11 18:01:33 -04:00
|
|
|
_, h = g.panel.GetCurrentFrameSize()
|
2020-07-26 14:52:54 -04:00
|
|
|
|
2019-12-28 23:32:53 -05:00
|
|
|
g.panel.SetPosition(x, y+h)
|
2020-07-26 14:52:54 -04:00
|
|
|
|
|
|
|
if err := g.panel.Render(target); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-12-28 23:32:53 -05:00
|
|
|
y += h
|
|
|
|
|
|
|
|
// Bottom right
|
2020-10-25 03:42:31 -04:00
|
|
|
if err := g.panel.SetCurrentFrame(frameInventoryBottomRight); err != nil {
|
2020-07-26 14:52:54 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-11 18:01:33 -04:00
|
|
|
_, h = g.panel.GetCurrentFrameSize()
|
2019-12-28 23:32:53 -05:00
|
|
|
g.panel.SetPosition(x, y+h)
|
2020-07-26 14:52:54 -04:00
|
|
|
|
|
|
|
if err := g.panel.Render(target); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-12-28 23:32:53 -05:00
|
|
|
|
|
|
|
// Bottom left
|
2020-10-25 03:42:31 -04:00
|
|
|
if err := g.panel.SetCurrentFrame(frameInventoryBottomLeft); err != nil {
|
2020-07-26 14:52:54 -04:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2019-12-28 23:32:53 -05:00
|
|
|
w, h = g.panel.GetCurrentFrameSize()
|
2020-07-26 14:52:54 -04:00
|
|
|
|
2019-12-28 23:32:53 -05:00
|
|
|
g.panel.SetPosition(x-w, y+h)
|
2020-07-26 14:52:54 -04:00
|
|
|
|
|
|
|
if err := g.panel.Render(target); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-12-28 23:32:53 -05:00
|
|
|
|
|
|
|
g.grid.Render(target)
|
2020-07-26 14:52:54 -04:00
|
|
|
|
2020-08-03 13:44:00 -04:00
|
|
|
hovering := false
|
|
|
|
|
|
|
|
for idx := range g.grid.items {
|
|
|
|
item := g.grid.items[idx]
|
|
|
|
ix, iy := g.grid.SlotToScreen(item.InventoryGridSlot())
|
|
|
|
iw, ih := g.grid.sprites[item.GetItemCode()].GetCurrentFrameSize()
|
|
|
|
mx, my := g.lastMouseX, g.lastMouseY
|
|
|
|
hovering = hovering || ((mx > ix) && (mx < ix+iw) && (my > iy) && (my < iy+ih))
|
|
|
|
|
|
|
|
if hovering {
|
|
|
|
if !g.hovering {
|
|
|
|
// set the initial hover coordinates
|
|
|
|
// this is so that moving mouse doesnt move the description
|
|
|
|
g.hoverX, g.hoverY = mx, my
|
|
|
|
}
|
|
|
|
|
|
|
|
g.renderItemDescription(target, item)
|
2020-08-06 10:30:23 -04:00
|
|
|
|
2020-08-03 13:44:00 -04:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g.hovering = hovering
|
|
|
|
|
2020-07-26 14:52:54 -04:00
|
|
|
return nil
|
2019-12-28 23:32:53 -05:00
|
|
|
}
|
2020-08-03 13:44:00 -04:00
|
|
|
|
|
|
|
func (g *Inventory) renderItemDescription(target d2interface.Surface, i InventoryItem) {
|
|
|
|
lines := i.GetItemDescription()
|
|
|
|
|
|
|
|
maxW, maxH := 0, 0
|
|
|
|
_, iy := g.grid.SlotToScreen(i.InventoryGridSlot())
|
|
|
|
|
|
|
|
for idx := range lines {
|
|
|
|
w, h := g.hoverLabel.GetTextMetrics(lines[idx])
|
|
|
|
|
|
|
|
if maxW < w {
|
|
|
|
maxW = w
|
|
|
|
}
|
|
|
|
|
|
|
|
maxH += h
|
|
|
|
}
|
|
|
|
|
2020-10-25 03:42:31 -04:00
|
|
|
halfW, halfH := maxW>>1, maxH>>1
|
2020-08-06 10:30:23 -04:00
|
|
|
centerX, centerY := g.hoverX, iy-halfH
|
2020-08-03 13:44:00 -04:00
|
|
|
|
2020-10-25 03:42:31 -04:00
|
|
|
if (centerX + halfW) > screenWidth {
|
|
|
|
centerX = screenWidth - halfW
|
2020-08-03 13:44:00 -04:00
|
|
|
}
|
|
|
|
|
2020-10-25 03:42:31 -04:00
|
|
|
if (centerY + halfH) > screenHeight {
|
|
|
|
centerY = screenHeight - halfH
|
2020-08-03 13:44:00 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
target.PushTranslation(centerX, centerY)
|
2020-10-25 03:42:31 -04:00
|
|
|
defer target.Pop()
|
|
|
|
|
2020-08-03 13:44:00 -04:00
|
|
|
target.PushTranslation(-halfW, -halfH)
|
2020-10-25 03:42:31 -04:00
|
|
|
defer target.Pop()
|
|
|
|
|
|
|
|
target.DrawRect(maxW, maxH, d2util.Color(blackAlpha70))
|
|
|
|
|
2020-08-03 13:44:00 -04:00
|
|
|
target.PushTranslation(halfW, 0)
|
2020-10-25 03:42:31 -04:00
|
|
|
defer target.Pop()
|
2020-08-03 13:44:00 -04:00
|
|
|
|
|
|
|
for idx := range lines {
|
|
|
|
g.hoverLabel.SetText(lines[idx])
|
|
|
|
_, h := g.hoverLabel.GetTextMetrics(lines[idx])
|
|
|
|
g.hoverLabel.Render(target)
|
|
|
|
target.PushTranslation(0, h)
|
|
|
|
}
|
|
|
|
|
|
|
|
target.PopN(len(lines))
|
|
|
|
}
|