2020-06-21 18:40:37 -04:00
|
|
|
package d2mapentity
|
2019-11-10 03:36:53 -05:00
|
|
|
|
|
|
|
import (
|
2020-04-11 14:56:47 -04:00
|
|
|
"math/rand"
|
|
|
|
|
2020-09-20 17:52:01 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
|
|
|
|
|
2020-02-22 20:44:30 -05:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
2020-07-31 17:55:11 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
2020-07-23 12:56:50 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
|
2020-09-12 16:25:09 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2path"
|
2020-06-24 13:49:13 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
2019-11-10 03:36:53 -05:00
|
|
|
)
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// NPC is a passive complex entity with which the player can interact.
|
|
|
|
// For example, Deckard Cain.
|
2019-11-10 03:36:53 -05:00
|
|
|
type NPC struct {
|
2020-06-24 13:49:13 -04:00
|
|
|
mapEntity
|
2020-09-08 15:58:35 -04:00
|
|
|
Paths []d2path.Path
|
2020-07-17 18:51:19 -04:00
|
|
|
name string
|
2020-06-27 23:15:20 -04:00
|
|
|
composite *d2asset.Composite
|
|
|
|
action int
|
|
|
|
path int
|
|
|
|
repetitions int
|
2020-09-20 17:52:01 -04:00
|
|
|
monstatRecord *d2records.MonStatsRecord
|
|
|
|
monstatEx *d2records.MonStats2Record
|
2020-07-17 18:51:19 -04:00
|
|
|
HasPaths bool
|
|
|
|
isDone bool
|
2019-11-10 03:36:53 -05:00
|
|
|
}
|
|
|
|
|
2020-07-17 18:51:19 -04:00
|
|
|
const (
|
|
|
|
magicOffsetX = 5
|
|
|
|
magicOffsetScalarX = 8
|
|
|
|
magicOffsetScalarY = 16
|
|
|
|
minAnimationRepetitions = 3
|
|
|
|
maxAnimationRepetitions = 5
|
|
|
|
)
|
|
|
|
|
2020-07-01 00:06:06 -04:00
|
|
|
func selectEquip(slice []string) string {
|
|
|
|
if len(slice) != 0 {
|
2020-10-26 02:38:15 -04:00
|
|
|
// nolint:gosec // not concerned with crypto-strong randomness
|
2020-07-01 00:06:06 -04:00
|
|
|
return slice[rand.Intn(len(slice))]
|
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
2020-08-05 21:27:45 -04:00
|
|
|
// ID returns the NPC uuid
|
|
|
|
func (v *NPC) ID() string {
|
|
|
|
return v.mapEntity.uuid
|
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// Render renders this entity's animated composite.
|
2020-06-29 00:41:58 -04:00
|
|
|
func (v *NPC) Render(target d2interface.Surface) {
|
2020-07-13 09:06:50 -04:00
|
|
|
renderOffset := v.Position.RenderOffset()
|
2020-06-24 13:49:13 -04:00
|
|
|
target.PushTranslation(
|
2020-07-17 18:51:19 -04:00
|
|
|
int((renderOffset.X()-renderOffset.Y())*magicOffsetScalarY),
|
|
|
|
int(((renderOffset.X()+renderOffset.Y())*magicOffsetScalarX)-magicOffsetX),
|
2020-06-24 13:49:13 -04:00
|
|
|
)
|
2020-07-13 09:06:50 -04:00
|
|
|
|
2020-06-24 13:49:13 -04:00
|
|
|
defer target.Pop()
|
2020-07-17 18:51:19 -04:00
|
|
|
|
|
|
|
if v.composite.Render(target) != nil {
|
|
|
|
return
|
|
|
|
}
|
2020-06-24 13:49:13 -04:00
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// Path returns the current part of the entity's path.
|
2020-09-08 15:58:35 -04:00
|
|
|
func (v *NPC) Path() d2path.Path {
|
2019-12-06 09:44:52 -05:00
|
|
|
return v.Paths[v.path]
|
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// NextPath returns the next part of the entity's path.
|
2020-09-08 15:58:35 -04:00
|
|
|
func (v *NPC) NextPath() d2path.Path {
|
2019-12-06 09:44:52 -05:00
|
|
|
v.path++
|
|
|
|
if v.path == len(v.Paths) {
|
|
|
|
v.path = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
return v.Paths[v.path]
|
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// SetPaths sets the entity's paths to the given slice. It also sets flags
|
|
|
|
// on the entity indicating that it has paths and has completed the
|
|
|
|
// previous none.
|
2020-09-08 15:58:35 -04:00
|
|
|
func (v *NPC) SetPaths(paths []d2path.Path) {
|
2019-11-11 23:48:55 -05:00
|
|
|
v.Paths = paths
|
2019-12-06 09:44:52 -05:00
|
|
|
v.HasPaths = len(paths) > 0
|
2020-02-22 20:44:30 -05:00
|
|
|
v.isDone = true
|
2019-12-13 00:33:11 -05:00
|
|
|
}
|
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// Advance is called once per frame and processes a
|
|
|
|
// single game tick.
|
2019-12-13 00:33:11 -05:00
|
|
|
func (v *NPC) Advance(tickTime float64) {
|
2020-02-22 20:44:30 -05:00
|
|
|
v.Step(tickTime)
|
2020-07-17 18:51:19 -04:00
|
|
|
|
|
|
|
if err := v.composite.Advance(tickTime); err != nil {
|
|
|
|
return
|
|
|
|
}
|
2020-02-22 20:44:30 -05:00
|
|
|
|
|
|
|
if v.HasPaths && v.wait() {
|
2019-12-13 00:33:11 -05:00
|
|
|
// If at the target, set target to the next path.
|
2020-02-22 20:44:30 -05:00
|
|
|
v.isDone = false
|
2019-12-13 00:33:11 -05:00
|
|
|
path := v.NextPath()
|
2020-07-16 12:06:08 -04:00
|
|
|
v.setTarget(
|
2020-07-18 18:07:13 -04:00
|
|
|
path.Position,
|
2020-02-22 20:44:30 -05:00
|
|
|
v.next,
|
2019-12-13 00:33:11 -05:00
|
|
|
)
|
2020-07-09 16:11:01 -04:00
|
|
|
|
2020-02-22 20:44:30 -05:00
|
|
|
v.action = path.Action
|
2019-12-13 00:33:11 -05:00
|
|
|
}
|
2020-02-22 20:44:30 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// If an npc has a path to pause at each location.
|
|
|
|
// Waits for animation to end and all repetitions to be exhausted.
|
|
|
|
func (v *NPC) wait() bool {
|
|
|
|
return v.isDone && v.composite.GetPlayedCount() > v.repetitions
|
|
|
|
}
|
2019-12-13 00:33:11 -05:00
|
|
|
|
2020-02-22 20:44:30 -05:00
|
|
|
func (v *NPC) next() {
|
2020-07-17 18:51:19 -04:00
|
|
|
var newAnimationMode d2enum.MonsterAnimationMode
|
|
|
|
|
2020-02-22 20:44:30 -05:00
|
|
|
v.isDone = true
|
2020-10-26 02:38:15 -04:00
|
|
|
|
|
|
|
// nolint:gosec // not concerned with crypto-strong randomness
|
2020-07-17 18:51:19 -04:00
|
|
|
v.repetitions = minAnimationRepetitions + rand.Intn(maxAnimationRepetitions)
|
|
|
|
|
|
|
|
switch d2enum.NPCActionType(v.action) {
|
|
|
|
case d2enum.NPCActionSkill1:
|
2020-07-09 23:12:28 -04:00
|
|
|
newAnimationMode = d2enum.MonsterAnimationModeSkill1
|
2020-02-22 20:44:30 -05:00
|
|
|
v.repetitions = 0
|
2020-07-17 18:51:19 -04:00
|
|
|
case d2enum.NPCActionInvalid, d2enum.NPCAction1, d2enum.NPCAction2, d2enum.NPCAction3:
|
|
|
|
newAnimationMode = d2enum.MonsterAnimationModeNeutral
|
|
|
|
v.repetitions = 0
|
2020-02-22 20:44:30 -05:00
|
|
|
default:
|
2020-07-17 18:51:19 -04:00
|
|
|
newAnimationMode = d2enum.MonsterAnimationModeNeutral
|
2020-02-22 20:44:30 -05:00
|
|
|
v.repetitions = 0
|
2019-12-13 00:33:11 -05:00
|
|
|
}
|
2019-12-24 01:48:45 -05:00
|
|
|
|
2020-06-20 00:40:49 -04:00
|
|
|
if v.composite.GetAnimationMode() != newAnimationMode.String() {
|
2020-07-17 18:51:19 -04:00
|
|
|
if err := v.composite.SetMode(newAnimationMode, v.composite.GetWeaponClass()); err != nil {
|
|
|
|
return
|
|
|
|
}
|
2020-02-22 20:44:30 -05:00
|
|
|
}
|
2019-12-13 00:33:11 -05:00
|
|
|
}
|
2020-06-24 13:49:13 -04:00
|
|
|
|
|
|
|
// rotate sets direction and changes animation
|
|
|
|
func (v *NPC) rotate(direction int) {
|
|
|
|
var newMode d2enum.MonsterAnimationMode
|
2020-07-16 12:06:08 -04:00
|
|
|
if !v.atTarget() {
|
2020-07-09 23:12:28 -04:00
|
|
|
newMode = d2enum.MonsterAnimationModeWalk
|
2020-06-24 13:49:13 -04:00
|
|
|
} else {
|
2020-07-09 23:12:28 -04:00
|
|
|
newMode = d2enum.MonsterAnimationModeNeutral
|
2020-06-24 13:49:13 -04:00
|
|
|
}
|
|
|
|
|
2020-07-03 22:52:50 -04:00
|
|
|
if newMode.String() != v.composite.GetAnimationMode() {
|
2020-07-17 18:51:19 -04:00
|
|
|
if err := v.composite.SetMode(newMode, v.composite.GetWeaponClass()); err != nil {
|
|
|
|
return
|
|
|
|
}
|
2020-06-24 13:49:13 -04:00
|
|
|
}
|
|
|
|
|
2020-07-03 22:52:50 -04:00
|
|
|
if v.composite.GetDirection() != direction {
|
|
|
|
v.composite.SetDirection(direction)
|
|
|
|
}
|
2020-06-24 13:49:13 -04:00
|
|
|
}
|
2020-06-25 00:39:09 -04:00
|
|
|
|
2020-07-09 16:11:01 -04:00
|
|
|
// Selectable returns true if the object can be highlighted/selected.
|
2020-07-17 18:51:19 -04:00
|
|
|
func (v *NPC) Selectable() bool {
|
2020-06-27 18:58:41 -04:00
|
|
|
// is there something handy that determines selectable npc's?
|
2020-07-17 18:51:19 -04:00
|
|
|
return v.name != ""
|
2020-06-27 18:58:41 -04:00
|
|
|
}
|
|
|
|
|
2020-08-02 21:26:07 -04:00
|
|
|
// Label returns the NPC's in-game name (e.g. "Deckard Cain") or an empty string if it does not have a name.
|
|
|
|
func (v *NPC) Label() string {
|
2020-07-17 18:51:19 -04:00
|
|
|
return v.name
|
2020-06-25 00:39:09 -04:00
|
|
|
}
|
2020-07-21 08:51:09 -04:00
|
|
|
|
|
|
|
// GetPosition returns the NPC's position
|
|
|
|
func (v *NPC) GetPosition() d2vector.Position {
|
|
|
|
return v.mapEntity.Position
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetVelocity returns the NPC's velocity vector
|
|
|
|
func (v *NPC) GetVelocity() d2vector.Vector {
|
|
|
|
return v.mapEntity.velocity
|
|
|
|
}
|
2020-08-01 19:03:09 -04:00
|
|
|
|
|
|
|
// GetSize returns the current frame size
|
2020-09-12 16:25:09 -04:00
|
|
|
func (v *NPC) GetSize() (width, height int) {
|
2020-08-01 19:03:09 -04:00
|
|
|
return v.composite.GetSize()
|
|
|
|
}
|