simple inventory item descriptions (#672)

* adding simple inventory item descriptions

* adding method to identify items

* offset description so it doesn't overlap with item in inventory grid
This commit is contained in:
lord 2020-08-03 10:44:00 -07:00 committed by GitHub
parent 8560956f7f
commit 76ed8ff180
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 290 additions and 41 deletions

View File

@ -2,6 +2,9 @@ package diablo2item
import (
"fmt"
"math/rand"
"sort"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
@ -9,7 +12,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2stats/diablo2stats"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
"math/rand"
)
// PropertyPool is used for separating properties by their source
@ -67,6 +69,9 @@ type Item struct {
attributes *itemAttributes
GridX int
GridY int
sockets []*d2item.Item // there will be checks for handling the craziness this might entail
}
@ -695,6 +700,8 @@ func (i *Item) GetStatStrings() []string {
stats = diablo2stats.NewStatList(stats...).ReduceStats().Stats()
}
sort.Slice(stats, func(i, j int) bool { return stats[i].Priority() > stats[j].Priority() })
for statIdx := range stats {
statStr := stats[statIdx].String()
if statStr != "" {
@ -786,3 +793,144 @@ func findMatchingAffixes(
return result
}
// these functions are to satisfy the inventory grid item interface
func (i *Item) GetInventoryItemName() string {
return i.Label()
}
func (i *Item) GetInventoryItemType() d2enum.InventoryItemType {
typeCode := i.TypeRecord().Code
armorEquiv := d2datadict.ItemEquivalenciesByTypeCode["armo"]
weaponEquiv := d2datadict.ItemEquivalenciesByTypeCode["weap"]
for idx := range armorEquiv {
if armorEquiv[idx].Code == typeCode {
return d2enum.InventoryItemTypeArmor
}
}
for idx := range weaponEquiv {
if weaponEquiv[idx].Code == typeCode {
return d2enum.InventoryItemTypeWeapon
}
}
return d2enum.InventoryItemTypeItem
}
func (i *Item) InventoryGridSize() (int, int) {
r := i.CommonRecord()
return r.InventoryWidth, r.InventoryHeight
}
func (i *Item) GetItemCode() string {
return i.CommonRecord().Code
}
func (i *Item) Serialize() []byte {
panic("item serialization not yet implemented")
}
func (i *Item) InventoryGridSlot() (x, y int) {
return i.GridX, i.GridY
}
func (i *Item) SetInventoryGridSlot(x, y int) {
i.GridX, i.GridY = x, y
}
func (i *Item) GetInventoryGridSize() (int, int) {
return i.GridX, i.GridY
}
func (i *Item) Identify() *Item {
i.attributes.identitified = true
return i
}
// from a string table
const (
reqNotMet = "ItemStats1a" // "Requirements not met",
unidentified = "ItemStats1b" // "Unidentified",
charges = "ItemStats1c" // "Charges:",
durability = "ItemStats1d" // "Durability:",
reqStrength = "ItemStats1e" // "Required Strength:",
reqDexterity = "ItemStats1f" // "Required Dexterity:",
damage = "ItemStats1g" // "Damage:",
defense = "ItemStats1h" // "Defense:",
quantity = "ItemStats1i" // "Quantity:",
of = "ItemStats1j" // "of",
to = "to" // "to"
damage1h = "ItemStats1l" // "One-Hand Damage:",
damage2h = "ItemStats1m" // "Two-Hand Damage:",
damageThrow = "ItemStats1n" // "Throw Damage:",
damageSmite = "ItemStats1o" // "Smite Damage:",
reqLevel = "ItemStats1p" // "Required Level:",
)
func (i *Item) GetItemDescription() []string {
lines := make([]string, 0)
common := i.CommonRecord()
lines = append(lines, i.Label())
str := ""
if common.MinAC > 0 {
min, max := common.MinAC, common.MaxAC
str = fmt.Sprintf("%s %v %s %v", d2common.TranslateString(defense), min, d2common.TranslateString(to), max)
str = d2ui.ColorTokenize(str, d2ui.ColorTokenWhite)
lines = append(lines, str)
}
if common.MinDamage > 0 {
min, max := common.MinDamage, common.MaxDamage
str = fmt.Sprintf("%s %v %s %v", d2common.TranslateString(damage1h), min, d2common.TranslateString(to), max)
str = d2ui.ColorTokenize(str, d2ui.ColorTokenWhite)
lines = append(lines, str)
}
if common.Min2HandDamage > 0 {
min, max := common.Min2HandDamage, common.Max2HandDamage
str = fmt.Sprintf("%s %v %s %v", d2common.TranslateString(damage2h), min, d2common.TranslateString(to), max)
str = d2ui.ColorTokenize(str, d2ui.ColorTokenWhite)
lines = append(lines, str)
}
if common.MinMissileDamage > 0 {
min, max := common.MinMissileDamage, common.MaxMissileDamage
str = fmt.Sprintf("%s %v %s %v", d2common.TranslateString(damageThrow), min, d2common.TranslateString(to), max)
str = d2ui.ColorTokenize(str, d2ui.ColorTokenWhite)
lines = append(lines, str)
}
if common.RequiredStrength > 1 {
str = fmt.Sprintf("%s %v", d2common.TranslateString(reqStrength), common.RequiredStrength)
str = d2ui.ColorTokenize(str, d2ui.ColorTokenWhite)
lines = append(lines, str)
}
if common.RequiredDexterity > 1 {
str = fmt.Sprintf("%s %v", d2common.TranslateString(reqDexterity), common.RequiredDexterity)
str = d2ui.ColorTokenize(str, d2ui.ColorTokenWhite)
lines = append(lines, str)
}
if common.RequiredLevel > 1 {
str = fmt.Sprintf("%s %v", d2common.TranslateString(reqLevel), common.RequiredLevel)
str = d2ui.ColorTokenize(str, d2ui.ColorTokenWhite)
lines = append(lines, str)
}
statStrings := i.GetStatStrings()
for _, statStr := range statStrings {
str = d2ui.ColorTokenize(statStr, d2ui.ColorTokenBlue)
lines = append(lines, str)
}
return lines
}

View File

@ -11,4 +11,5 @@ type Stat interface {
String() string
Values() []StatValue
SetValues(...StatValue)
Priority() int
}

View File

@ -279,6 +279,8 @@ func (g *GameControls) OnMouseMove(event d2interface.MouseMoveEvent) bool {
mx, my := event.X(), event.Y()
g.lastMouseX = mx
g.lastMouseY = my
g.inventory.lastMouseX = mx
g.inventory.lastMouseY = my
for i := range g.actionableRegions {
// Mouse over a game control element
@ -454,8 +456,8 @@ func (g *GameControls) Render(target d2interface.Surface) error {
}
}
g.inventory.Render(target)
g.heroStatsPanel.Render(target)
g.inventory.Render(target)
width, height := target.GetSize()
offset := 0

View File

@ -1,28 +1,42 @@
package d2player
import (
"image/color"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2item/diablo2item"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
type Inventory struct {
frame *d2ui.Sprite
panel *d2ui.Sprite
grid *ItemGrid
originX int
originY int
isOpen bool
frame *d2ui.Sprite
panel *d2ui.Sprite
grid *ItemGrid
hoverLabel *d2ui.Label
hoverX, hoverY int
hovering bool
originX, originY int
lastMouseX, lastMouseY int
isOpen bool
}
func NewInventory(record *d2datadict.InventoryRecord) *Inventory {
hoverLabel := d2ui.CreateLabel(d2resource.FontFormal11, d2resource.PaletteStatic)
hoverLabel.Alignment = d2gui.HorizontalAlignCenter
return &Inventory{
grid: NewItemGrid(record),
originX: record.Panel.Left,
grid: NewItemGrid(record),
originX: record.Panel.Left,
hoverLabel: &hoverLabel,
// originY: record.Panel.Top,
originY: 0, // expansion data has these all offset by +60 ...
}
@ -51,23 +65,23 @@ func (g *Inventory) Load() {
animation, _ = d2asset.LoadAnimation(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
g.panel, _ = d2ui.LoadSprite(animation)
items := []InventoryItem{
d2inventory.GetWeaponItemByCode("wnd"),
d2inventory.GetWeaponItemByCode("sst"),
d2inventory.GetWeaponItemByCode("jav"),
d2inventory.GetArmorItemByCode("buc"),
d2inventory.GetWeaponItemByCode("clb"),
diablo2item.NewItem("kit", "Crimson", "of the Bat", "of Frost").Identify(),
diablo2item.NewItem("rin", "Steel", "of Shock").Identify(),
diablo2item.NewItem("jav").Identify(),
diablo2item.NewItem("buc").Identify(),
//diablo2item.NewItem("Arctic Furs", "qui"),
// TODO: Load the player's actual items
}
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotLeftArm, d2inventory.GetWeaponItemByCode("wnd"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotRightArm, d2inventory.GetArmorItemByCode("buc"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotHead, d2inventory.GetArmorItemByCode("crn"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotTorso, d2inventory.GetArmorItemByCode("plt"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotLegs, d2inventory.GetArmorItemByCode("vbt"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotBelt, d2inventory.GetArmorItemByCode("vbl"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotGloves, d2inventory.GetArmorItemByCode("lgl"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotLeftHand, d2inventory.GetMiscItemByCode("rin"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotRightHand, d2inventory.GetMiscItemByCode("rin"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotNeck, d2inventory.GetMiscItemByCode("amu"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotLeftArm, diablo2item.NewItem("wnd"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotRightArm, diablo2item.NewItem("buc"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotHead, diablo2item.NewItem("crn"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotTorso, diablo2item.NewItem("plt"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotLegs, diablo2item.NewItem("vbt"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotBelt, diablo2item.NewItem("vbl"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotGloves, diablo2item.NewItem("lgl"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotLeftHand, diablo2item.NewItem("rin"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotRightHand, diablo2item.NewItem("rin"))
g.grid.ChangeEquippedSlot(d2enum.EquippedSlotNeck, diablo2item.NewItem("amu"))
// TODO: Load the player's actual items
g.grid.Add(items...)
}
@ -215,5 +229,71 @@ func (g *Inventory) Render(target d2interface.Surface) error {
g.grid.Render(target)
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)
break
}
}
g.hovering = hovering
return nil
}
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
}
halfW, halfH := maxW/2, maxH/2
centerX, centerY := g.hoverX, iy - halfH
if (centerX + halfW) > 800 {
centerX = 800 - halfW
}
if (centerY + halfH) > 600 {
centerY = 600 - halfH
}
target.PushTranslation(centerX, centerY)
target.PushTranslation(-halfW, -halfH)
target.DrawRect(maxW, maxH, color.RGBA{0, 0, 0, uint8(200)})
target.PushTranslation(halfW, 0)
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))
target.PopN(3)
}

View File

@ -3,9 +3,9 @@ package d2player
import (
"errors"
"fmt"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
@ -23,8 +23,9 @@ const cellPadding = 1
type InventoryItem interface {
InventoryGridSize() (width int, height int)
GetItemCode() string
InventoryGridSlot() (x int, y int)
SetInventoryGridSlot(x int, y int)
InventoryGridSlot() (x, y int)
SetInventoryGridSlot(x, y int)
GetItemDescription() []string
}
var ErrorInventoryFull = errors.New("inventory full")
@ -44,6 +45,7 @@ type ItemGrid struct {
func NewItemGrid(record *d2datadict.InventoryRecord) *ItemGrid {
grid := record.Grid
return &ItemGrid{
width: grid.Box.Width,
height: grid.Box.Height,
@ -58,16 +60,18 @@ func NewItemGrid(record *d2datadict.InventoryRecord) *ItemGrid {
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 {
func (g *ItemGrid) GetSlot(x, y int) InventoryItem {
for _, item := range g.items {
slotX, slotY := item.InventoryGridSlot()
width, height := item.InventoryGridSize()
@ -90,6 +94,7 @@ func (g *ItemGrid) ChangeEquippedSlot(slot d2enum.EquippedSlot, item InventoryIt
// 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 {
@ -115,14 +120,17 @@ func (g *ItemGrid) loadItem(item InventoryItem) {
fmt.Sprintf("/data/global/items/inv%s.dc6", item.GetItemCode()),
d2resource.PaletteSky,
)
if err != nil {
log.Printf("failed to load sprite for item (%s): %v", item.GetItemCode(), err)
return
}
itemSprite, err = d2ui.LoadSprite(animation)
if err != nil {
log.Printf("Failed to load sprite, error: " + err.Error())
}
g.sprites[item.GetItemCode()] = itemSprite
}
}
@ -132,6 +140,7 @@ func (g *ItemGrid) Load(items ...InventoryItem) {
for _, item := range items {
g.loadItem(item)
}
for _, eq := range g.equipmentSlots {
if eq.item != nil {
g.loadItem(eq.item)
@ -149,6 +158,7 @@ func (g *ItemGrid) add(item InventoryItem) bool {
}
g.set(x, y, item)
return true
}
}
@ -157,7 +167,7 @@ func (g *ItemGrid) add(item InventoryItem) bool {
}
// 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 {
func (g *ItemGrid) canFit(x, y int, item InventoryItem) bool {
insertWidth, insertHeight := item.InventoryGridSize()
if x+insertWidth > g.width || y+insertHeight > g.height {
return false
@ -178,15 +188,17 @@ func (g *ItemGrid) canFit(x int, y int, item InventoryItem) bool {
return true
}
func (g *ItemGrid) Set(x int, y int, item InventoryItem) error {
func (g *ItemGrid) Set(x, y int, item InventoryItem) error {
if !g.canFit(x, y, item) {
return fmt.Errorf("can not set item (%s) to position (%v, %v)", item.GetItemCode(), x, y)
}
g.set(x, y, item)
return nil
}
func (g *ItemGrid) set(x int, y int, item InventoryItem) {
func (g *ItemGrid) set(x, y int, item InventoryItem) {
item.SetInventoryGridSlot(x, y)
g.items = append(g.items, item)
@ -196,10 +208,12 @@ func (g *ItemGrid) set(x int, y int, item InventoryItem) {
// 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++
}
@ -207,7 +221,7 @@ func (g *ItemGrid) Remove(item InventoryItem) {
g.items = g.items[:n]
}
func (g *ItemGrid) renderItem(item InventoryItem, target d2interface.Surface, x int, y int) {
func (g *ItemGrid) renderItem(item InventoryItem, target d2interface.Surface, x, y int) {
itemSprite := g.sprites[item.GetItemCode()]
if itemSprite != nil {
itemSprite.SetPosition(x, y)
@ -226,19 +240,23 @@ func (g *ItemGrid) renderInventoryItems(target d2interface.Surface) {
itemSprite := g.sprites[item.GetItemCode()]
slotX, slotY := g.SlotToScreen(item.InventoryGridSlot())
_, h := itemSprite.GetCurrentFrameSize()
slotY = slotY + h
slotY += h
g.renderItem(item, target, slotX, slotY)
}
}
func (g *ItemGrid) renderEquippedItems(target d2interface.Surface) {
for _, eq := range g.equipmentSlots {
if eq.item != nil {
itemSprite := g.sprites[eq.item.GetItemCode()]
itemWidth, itemHeight := itemSprite.GetCurrentFrameSize()
var x = eq.x + ((eq.width - itemWidth) / 2)
var y = eq.y - ((eq.height - itemHeight) / 2)
g.renderItem(eq.item, target, x, y)
if eq.item == nil {
continue
}
itemSprite := g.sprites[eq.item.GetItemCode()]
itemWidth, itemHeight := itemSprite.GetCurrentFrameSize()
x := eq.x + ((eq.width - itemWidth) / 2)
y := eq.y - ((eq.height - itemHeight) / 2)
g.renderItem(eq.item, target, x, y)
}
}