From 1275a7f65457f740bb565d07456a8cbac939df75 Mon Sep 17 00:00:00 2001 From: lord Date: Fri, 31 Jul 2020 14:55:11 -0700 Subject: [PATCH] D2item hover highlight + name (#656) * added highlight to animated entity * moving provider functions for item, missile, npc, player into package export file d2mapentity.go * changed `Create` to `New` in map entity provider functions * add item highlight on hover * add Name method to item entity --- d2core/d2map/d2mapentity/animated_entity.go | 26 +-- d2core/d2map/d2mapentity/d2mapentity.go | 176 ++++++++++++++++++++ d2core/d2map/d2mapentity/item.go | 47 ++---- d2core/d2map/d2mapentity/map_entity.go | 3 + d2core/d2map/d2mapentity/missile.go | 34 +--- d2core/d2map/d2mapentity/npc.go | 44 +---- d2core/d2map/d2mapentity/player.go | 55 ------ d2core/d2map/d2mapstamp/stamp.go | 2 +- d2game/d2gamescreen/character_select.go | 2 +- d2networking/d2client/game_client.go | 6 +- 10 files changed, 216 insertions(+), 179 deletions(-) create mode 100644 d2core/d2map/d2mapentity/d2mapentity.go diff --git a/d2core/d2map/d2mapentity/animated_entity.go b/d2core/d2map/d2mapentity/animated_entity.go index d987cda8..703471c7 100644 --- a/d2core/d2map/d2mapentity/animated_entity.go +++ b/d2core/d2map/d2mapentity/animated_entity.go @@ -9,22 +9,13 @@ import ( // AnimatedEntity represents an animation that can be projected onto the map. type AnimatedEntity struct { mapEntity + animation d2interface.Animation + direction int action int repetitions int - animation d2interface.Animation -} - -// CreateAnimatedEntity creates an instance of AnimatedEntity -func CreateAnimatedEntity(x, y int, animation d2interface.Animation) *AnimatedEntity { - entity := &AnimatedEntity{ - mapEntity: newMapEntity(x, y), - animation: animation, - } - entity.mapEntity.directioner = entity.rotate - - return entity + highlight bool } // Render draws this animated entity onto the target @@ -37,6 +28,12 @@ func (ae *AnimatedEntity) Render(target d2interface.Surface) { defer target.Pop() + if ae.highlight { + target.PushBrightness(2) + defer target.Pop() + ae.highlight = false + } + if err := ae.animation.Render(target); err != nil { fmt.Printf("failed to render animated entity, err: %v\n", err) } @@ -63,3 +60,8 @@ func (ae *AnimatedEntity) Advance(elapsed float64) { fmt.Printf("failed to advance the animation, err: %v\n", err) } } + +// SetHighlight sets the highlight state of the animated entity +func (ae *AnimatedEntity) SetHighlight(set bool) { + ae.highlight = set +} diff --git a/d2core/d2map/d2mapentity/d2mapentity.go b/d2core/d2map/d2mapentity/d2mapentity.go new file mode 100644 index 00000000..08724ba9 --- /dev/null +++ b/d2core/d2map/d2mapentity/d2mapentity.go @@ -0,0 +1,176 @@ +package d2mapentity + +import ( + "errors" + "fmt" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + "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/d2hero" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2item/diablo2item" +) + +// NewAnimatedEntity creates an instance of AnimatedEntity +func NewAnimatedEntity(x, y int, animation d2interface.Animation) *AnimatedEntity { + entity := &AnimatedEntity{ + mapEntity: newMapEntity(x, y), + animation: animation, + } + entity.mapEntity.directioner = entity.rotate + + return entity +} + +// NewPlayer creates a new player entity and returns a pointer to it. +func NewPlayer(id, name string, x, y, direction int, heroType d2enum.Hero, + stats *d2hero.HeroStatsState, equipment *d2inventory.CharacterEquipment) *Player { + layerEquipment := &[d2enum.CompositeTypeMax]string{ + d2enum.CompositeTypeHead: equipment.Head.GetArmorClass(), + d2enum.CompositeTypeTorso: equipment.Torso.GetArmorClass(), + d2enum.CompositeTypeLegs: equipment.Legs.GetArmorClass(), + d2enum.CompositeTypeRightArm: equipment.RightArm.GetArmorClass(), + d2enum.CompositeTypeLeftArm: equipment.LeftArm.GetArmorClass(), + d2enum.CompositeTypeRightHand: equipment.RightHand.GetItemCode(), + d2enum.CompositeTypeLeftHand: equipment.LeftHand.GetItemCode(), + d2enum.CompositeTypeShield: equipment.Shield.GetItemCode(), + } + + composite, err := d2asset.LoadComposite(d2enum.ObjectTypePlayer, heroType.GetToken(), + d2resource.PaletteUnits) + if err != nil { + panic(err) + } + + stats.NextLevelExp = d2datadict.GetExperienceBreakpoint(heroType, stats.Level) + stats.Stamina = stats.MaxStamina + + result := &Player{ + ID: id, + mapEntity: newMapEntity(x, y), + composite: composite, + Equipment: equipment, + Stats: stats, + name: name, + Class: heroType, + //nameLabel: d2ui.CreateLabel(d2resource.FontFormal11, d2resource.PaletteStatic), + isRunToggled: true, + isInTown: true, + isRunning: true, + } + result.SetSpeed(baseRunSpeed) + result.mapEntity.directioner = result.rotate + err = composite.SetMode(d2enum.PlayerAnimationModeTownNeutral, equipment.RightHand.GetWeaponClass()) + + if err != nil { + panic(err) + } + + composite.SetDirection(direction) + + if err := composite.Equip(layerEquipment); err != nil { + fmt.Printf("failed to equip, err: %v\n", err) + } + + return result +} + +// NewMissile creates a new Missile and initializes it's animation. +func NewMissile(x, y int, record *d2datadict.MissileRecord) (*Missile, error) { + animation, err := d2asset.LoadAnimation( + fmt.Sprintf("%s/%s.dcc", d2resource.MissileData, record.Animation.CelFileName), + d2resource.PaletteUnits, + ) + if err != nil { + return nil, err + } + + if record.Animation.HasSubLoop { + animation.SetSubLoop(record.Animation.SubStartingFrame, record.Animation.SubEndingFrame) + } + + animation.SetEffect(d2enum.DrawEffectModulate) + animation.SetPlayLoop(record.Animation.LoopAnimation) + animation.PlayForward() + entity := NewAnimatedEntity(x, y, animation) + + result := &Missile{ + AnimatedEntity: entity, + record: record, + } + result.Speed = float64(record.Velocity) + + return result, nil +} + +// NewItem creates an item map entity +func NewItem(x, y int, codes ...string) (*Item, error) { + item := diablo2item.NewItem(codes...) + + if item == nil { + return nil, errors.New(errInvalidItemCodes) + } + + filename := item.CommonRecord().FlippyFile + filepath := fmt.Sprintf("%s/%s.DC6", d2resource.ItemGraphics, filename) + animation, err := d2asset.LoadAnimation(filepath, d2resource.PaletteUnits) + + if err != nil { + return nil, err + } + + animation.PlayForward() + animation.SetPlayLoop(false) + entity := NewAnimatedEntity(x*5, y*5, animation) + + result := &Item{ + AnimatedEntity: entity, + Item: item, + } + + return result, nil +} + +// NewNPC creates a new NPC and returns a pointer to it. +func NewNPC(x, y int, monstat *d2datadict.MonStatsRecord, direction int) (*NPC, error) { + result := &NPC{ + mapEntity: newMapEntity(x, y), + HasPaths: false, + monstatRecord: monstat, + monstatEx: d2datadict.MonStats2[monstat.ExtraDataKey], + } + + var equipment [16]string + + for compType, opts := range result.monstatEx.EquipmentOptions { + equipment[compType] = selectEquip(opts) + } + + composite, _ := d2asset.LoadComposite(d2enum.ObjectTypeCharacter, monstat.AnimationDirectoryToken, + d2resource.PaletteUnits) + result.composite = composite + + if err := composite.SetMode(d2enum.MonsterAnimationModeNeutral, + result.monstatEx.BaseWeaponClass); err != nil { + return nil, err + } + + if err := composite.Equip(&equipment); err != nil { + return nil, err + } + + result.SetSpeed(float64(monstat.SpeedBase)) + result.mapEntity.directioner = result.rotate + + result.composite.SetDirection(direction) + + if result.monstatRecord != nil && result.monstatRecord.IsInteractable { + result.name = d2common.TranslateString(result.monstatRecord.NameString) + } + + return result, nil +} diff --git a/d2core/d2map/d2mapentity/item.go b/d2core/d2map/d2mapentity/item.go index 0812b556..2ac8e6a2 100644 --- a/d2core/d2map/d2mapentity/item.go +++ b/d2core/d2map/d2mapentity/item.go @@ -1,12 +1,7 @@ package d2mapentity import ( - "errors" - "fmt" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2item/diablo2item" ) @@ -14,43 +9,33 @@ const ( errInvalidItemCodes = "invalid item codes supplied" ) +// Item is a map entity for an item type Item struct { *AnimatedEntity Item *diablo2item.Item } +// GetPosition returns the item position vector func (i *Item) GetPosition() d2vector.Position { return i.AnimatedEntity.Position } +// GetVelocity returns the item velocity vector func (i *Item) GetVelocity() d2vector.Vector { return i.AnimatedEntity.velocity } -func CreateItem(x, y int, codes ...string) (*Item, error) { - item := diablo2item.NewItem(codes...) - - if item == nil { - return nil, errors.New(errInvalidItemCodes) - } - - animation, err := d2asset.LoadAnimation( - fmt.Sprintf("%s/%s.DC6", d2resource.ItemGraphics, item.CommonRecord().FlippyFile), - d2resource.PaletteUnits, - ) - - if err != nil { - return nil, err - } - - animation.PlayForward() - animation.SetPlayLoop(false) - entity := CreateAnimatedEntity(x*5, y*5, animation) - - result := &Item{ - AnimatedEntity: entity, - Item: item, - } - - return result, nil +// Selectable always returns true for items +func (i *Item) Selectable() bool { + return true +} + +// Highlight sets the highlight flag for a single render tick +func (i *Item) Highlight() { + i.AnimatedEntity.highlight = true +} + +// Name returns the item name +func (i *Item) Name() string { + return i.Item.Name() } diff --git a/d2core/d2map/d2mapentity/map_entity.go b/d2core/d2map/d2mapentity/map_entity.go index 1693b0f7..775410b7 100644 --- a/d2core/d2map/d2mapentity/map_entity.go +++ b/d2core/d2map/d2mapentity/map_entity.go @@ -16,6 +16,8 @@ type mapEntity struct { done func() directioner func(direction int) + + highlight bool } // newMapEntity creates an instance of mapEntity @@ -197,6 +199,7 @@ func (m *mapEntity) Name() string { // Highlight is not currently implemented. func (m *mapEntity) Highlight() { + m.highlight = true } // Selectable returns true if the object can be highlighted/selected. diff --git a/d2core/d2map/d2mapentity/missile.go b/d2core/d2map/d2mapentity/missile.go index 44f997bb..6ea6caca 100644 --- a/d2core/d2map/d2mapentity/missile.go +++ b/d2core/d2map/d2mapentity/missile.go @@ -1,15 +1,10 @@ package d2mapentity import ( - "fmt" "math" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" ) // Missile is a simple animated entity representing a projectile, @@ -29,33 +24,6 @@ func (m *Missile) GetVelocity() d2vector.Vector { return m.AnimatedEntity.velocity } -// CreateMissile creates a new Missile and initializes it's animation. -func CreateMissile(x, y int, record *d2datadict.MissileRecord) (*Missile, error) { - animation, err := d2asset.LoadAnimation( - fmt.Sprintf("%s/%s.dcc", d2resource.MissileData, record.Animation.CelFileName), - d2resource.PaletteUnits, - ) - if err != nil { - return nil, err - } - - if record.Animation.HasSubLoop { - animation.SetSubLoop(record.Animation.SubStartingFrame, record.Animation.SubEndingFrame) - } - - animation.SetEffect(d2enum.DrawEffectModulate) - animation.SetPlayLoop(record.Animation.LoopAnimation) - animation.PlayForward() - entity := CreateAnimatedEntity(x, y, animation) - - result := &Missile{ - AnimatedEntity: entity, - record: record, - } - result.Speed = float64(record.Velocity) - - return result, nil -} // SetRadians adjusts the entity target based on it's range, rotating it's // current destination by the value of angle in radians. diff --git a/d2core/d2map/d2mapentity/npc.go b/d2core/d2map/d2mapentity/npc.go index d8c7e745..0a9a8cb2 100644 --- a/d2core/d2map/d2mapentity/npc.go +++ b/d2core/d2map/d2mapentity/npc.go @@ -3,13 +3,11 @@ package d2mapentity import ( "math/rand" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" ) @@ -37,46 +35,6 @@ const ( maxAnimationRepetitions = 5 ) -// CreateNPC creates a new NPC and returns a pointer to it. -func CreateNPC(x, y int, monstat *d2datadict.MonStatsRecord, direction int) (*NPC, error) { - result := &NPC{ - mapEntity: newMapEntity(x, y), - HasPaths: false, - monstatRecord: monstat, - monstatEx: d2datadict.MonStats2[monstat.ExtraDataKey], - } - - var equipment [16]string - - for compType, opts := range result.monstatEx.EquipmentOptions { - equipment[compType] = selectEquip(opts) - } - - composite, _ := d2asset.LoadComposite(d2enum.ObjectTypeCharacter, monstat.AnimationDirectoryToken, - d2resource.PaletteUnits) - result.composite = composite - - if err := composite.SetMode(d2enum.MonsterAnimationModeNeutral, - result.monstatEx.BaseWeaponClass); err != nil { - return nil, err - } - - if err := composite.Equip(&equipment); err != nil { - return nil, err - } - - result.SetSpeed(float64(monstat.SpeedBase)) - result.mapEntity.directioner = result.rotate - - result.composite.SetDirection(direction) - - if result.monstatRecord != nil && result.monstatRecord.IsInteractable { - result.name = d2common.TranslateString(result.monstatRecord.NameString) - } - - return result, nil -} - func selectEquip(slice []string) string { if len(slice) != 0 { return slice[rand.Intn(len(slice))] diff --git a/d2core/d2map/d2mapentity/player.go b/d2core/d2map/d2mapentity/player.go index 9e453e6a..e953b412 100644 --- a/d2core/d2map/d2mapentity/player.go +++ b/d2core/d2map/d2mapentity/player.go @@ -3,11 +3,9 @@ package d2mapentity import ( "fmt" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory" @@ -37,59 +35,6 @@ type Player struct { const baseWalkSpeed = 6.0 const baseRunSpeed = 9.0 -// CreatePlayer creates a new player entity and returns a pointer to it. -func CreatePlayer(id, name string, x, y, direction int, heroType d2enum.Hero, - stats *d2hero.HeroStatsState, equipment *d2inventory.CharacterEquipment) *Player { - layerEquipment := &[d2enum.CompositeTypeMax]string{ - d2enum.CompositeTypeHead: equipment.Head.GetArmorClass(), - d2enum.CompositeTypeTorso: equipment.Torso.GetArmorClass(), - d2enum.CompositeTypeLegs: equipment.Legs.GetArmorClass(), - d2enum.CompositeTypeRightArm: equipment.RightArm.GetArmorClass(), - d2enum.CompositeTypeLeftArm: equipment.LeftArm.GetArmorClass(), - d2enum.CompositeTypeRightHand: equipment.RightHand.GetItemCode(), - d2enum.CompositeTypeLeftHand: equipment.LeftHand.GetItemCode(), - d2enum.CompositeTypeShield: equipment.Shield.GetItemCode(), - } - - composite, err := d2asset.LoadComposite(d2enum.ObjectTypePlayer, heroType.GetToken(), - d2resource.PaletteUnits) - if err != nil { - panic(err) - } - - stats.NextLevelExp = d2datadict.GetExperienceBreakpoint(heroType, stats.Level) - stats.Stamina = stats.MaxStamina - - result := &Player{ - ID: id, - mapEntity: newMapEntity(x, y), - composite: composite, - Equipment: equipment, - Stats: stats, - name: name, - Class: heroType, - //nameLabel: d2ui.CreateLabel(d2resource.FontFormal11, d2resource.PaletteStatic), - isRunToggled: true, - isInTown: true, - isRunning: true, - } - result.SetSpeed(baseRunSpeed) - result.mapEntity.directioner = result.rotate - err = composite.SetMode(d2enum.PlayerAnimationModeTownNeutral, equipment.RightHand.GetWeaponClass()) - - if err != nil { - panic(err) - } - - composite.SetDirection(direction) - - if err := composite.Equip(layerEquipment); err != nil { - fmt.Printf("failed to equip, err: %v\n", err) - } - - return result -} - // SetIsInTown sets a flag indicating that the player is in town. func (p *Player) SetIsInTown(isInTown bool) { p.isInTown = isInTown diff --git a/d2core/d2map/d2mapstamp/stamp.go b/d2core/d2map/d2mapstamp/stamp.go index 936d736c..e6d541b8 100644 --- a/d2core/d2map/d2mapstamp/stamp.go +++ b/d2core/d2map/d2mapstamp/stamp.go @@ -133,7 +133,7 @@ func (mr *Stamp) Entities(tileOffsetX, tileOffsetY int) []d2interface.MapEntity if monstat != nil { // Temorary use of Lookup. npcX, npcY := (tileOffsetX*5)+object.X, (tileOffsetY*5)+object.Y - npc, err := d2mapentity.CreateNPC(npcX, npcY, monstat, 0) + npc, err := d2mapentity.NewNPC(npcX, npcY, monstat, 0) if err == nil { npc.SetPaths(convertPaths(tileOffsetX, tileOffsetY, object.Paths)) diff --git a/d2game/d2gamescreen/character_select.go b/d2game/d2gamescreen/character_select.go index 6b1091b7..429c7872 100644 --- a/d2game/d2gamescreen/character_select.go +++ b/d2game/d2gamescreen/character_select.go @@ -297,7 +297,7 @@ func (v *CharacterSelect) updateCharacterBoxes() { equipment := d2inventory.HeroObjects[heroType] // TODO: Generate or load the object from the actual player data... - v.characterImage[i] = d2mapentity.CreatePlayer("", "", 0, 0, 0, + v.characterImage[i] = d2mapentity.NewPlayer("", "", 0, 0, 0, v.gameStates[idx].HeroType, v.gameStates[idx].Stats, &equipment, diff --git a/d2networking/d2client/game_client.go b/d2networking/d2client/game_client.go index a977dd07..2ad53a39 100644 --- a/d2networking/d2client/game_client.go +++ b/d2networking/d2client/game_client.go @@ -168,7 +168,7 @@ func (g *GameClient) handleUpdateServerInfoPacket(packet d2netpacket.NetPacket) func (g *GameClient) handleAddPlayerPacket(packet d2netpacket.NetPacket) error { player := packet.PacketData.(d2netpacket.AddPlayerPacket) - newPlayer := d2mapentity.CreatePlayer(player.ID, player.Name, player.X, player.Y, 0, + newPlayer := d2mapentity.NewPlayer(player.ID, player.Name, player.X, player.Y, 0, player.HeroType, player.Stats, &player.Equipment) g.Players[newPlayer.ID] = newPlayer @@ -179,7 +179,7 @@ func (g *GameClient) handleAddPlayerPacket(packet d2netpacket.NetPacket) error { func (g *GameClient) handleSpawnItemPacket(packet d2netpacket.NetPacket) error { item := packet.PacketData.(d2netpacket.SpawnItemPacket) - itemEntity, err := d2mapentity.CreateItem(item.X, item.Y, item.Codes...) + itemEntity, err := d2mapentity.NewItem(item.X, item.Y, item.Codes...) if err == nil { g.MapEngine.AddEntity(itemEntity) @@ -230,7 +230,7 @@ func (g *GameClient) handleCastSkillPacket(packet d2netpacket.NetPacket) error { player.ClearPath() // currently hardcoded to missile skill - missile, err := d2mapentity.CreateMissile( + missile, err := d2mapentity.NewMissile( int(player.Position.X()), int(player.Position.Y()), d2datadict.Missiles[playerCast.SkillID],