1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-11-19 10:56:07 -05:00
OpenDiablo2/d2core/d2map/d2mapentity/npc.go

192 lines
4.7 KiB
Go
Raw Normal View History

2020-06-21 18:40:37 -04:00
package d2mapentity
2019-11-10 03:36:53 -05:00
import (
"math/rand"
Removing d2datadict singletons (#738) * Remove weapons, armor, misc, itemCommon, itemTyps datadict singletons - removed loader calls from d2app - removed the HeroObjects singleton from `d2core/d2inventory` - added an InventoryItemFactory in d2inventory - package-level functions that use data records are now methods of the InventoryItemFactory - renamed ItemGenerator in d2item to ItemFactory - package-level functions that use records are now methods of ItemFactory - d2map.MapEntityFactory now has an item factory instance for creating items - fixed a bug in unique item record loader where it loaded an empty record - added a PlayerStateFactory for creating a player state (uses the asset manager) - updated the test inventory/equipment code in d2player to handle errors from the ItemFactory - character select and character creation screens have a player state and inventory item factory - updated item tests to use the item factory * minor edit * Removed d2datadict.Experience singleton added a HeroStatsFactory, much like the other factories. The factory gets an asset manager reference in order to use data records. * removed d2datadict.AutoMagic singleton * removed d2datadict.AutoMap singleton * removed d2datadict.BodyLocations singleton * removed d2datadict.Books singleton * Removed singletons for level records - removed loader calls in d2app - changed type references from d2datadict to d2records - added a `MapGenerator` in d2mapgen which uses thew asset manager and map engine - package-level map generation functions are now MapGenerator methods - `d2datadict.GetLevelDetails(id int)` is now a method of the RecordManager * remove SkillCalc and MissileCalc singletons * Removed CharStats and ItemStatCost singletons - added an ItemStatFactory which uses the asset manager to create stats - package-level functions for stats in d2item are now StatFactory methods - changed type references from d2datadict to d2records - `d2player.GetAllPlayerStates` is now a method of the `PlayerStateFactory` * Removed DkillDesc and Skills singletons from d2datadict - removed loader calls from d2app - diablo2stats.Stat instances are given a reference to the factory for doing record lookups * update the stats test to use mock a asset manager and stat factory * fixed diablo2stats tests and diablo2item tests * removed CompCodes singleton from d2datadict * remove cubemain singleton from d2datadict * removed DifficultyLevels singleton from d2datadict * removed ElemTypes singleton from d2datadict * removed events.go loader from d2datadict (was unused) * removed Gems singleton from d2datadict * removed Hireling and Inventory singletons from d2datadict * removed MagicPrefix and MagicSuffix singletons from d2datadict * removed ItemRatios singleton from d2datadict * removed Missiles singleton from d2datadict * removed MonModes singleton * Removed all monster and npc singletons from d2datadict - MapStamp instances now get a reference to their factory for doing record lookups * removed SoundEntry and SoundEnviron singletons from d2datadict
2020-09-20 17:52:01 -04:00
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"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"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
2019-11-10 03:36:53 -05: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 {
mapEntity
Paths []d2path.Path
name string
composite *d2asset.Composite
action int
path int
repetitions int
Removing d2datadict singletons (#738) * Remove weapons, armor, misc, itemCommon, itemTyps datadict singletons - removed loader calls from d2app - removed the HeroObjects singleton from `d2core/d2inventory` - added an InventoryItemFactory in d2inventory - package-level functions that use data records are now methods of the InventoryItemFactory - renamed ItemGenerator in d2item to ItemFactory - package-level functions that use records are now methods of ItemFactory - d2map.MapEntityFactory now has an item factory instance for creating items - fixed a bug in unique item record loader where it loaded an empty record - added a PlayerStateFactory for creating a player state (uses the asset manager) - updated the test inventory/equipment code in d2player to handle errors from the ItemFactory - character select and character creation screens have a player state and inventory item factory - updated item tests to use the item factory * minor edit * Removed d2datadict.Experience singleton added a HeroStatsFactory, much like the other factories. The factory gets an asset manager reference in order to use data records. * removed d2datadict.AutoMagic singleton * removed d2datadict.AutoMap singleton * removed d2datadict.BodyLocations singleton * removed d2datadict.Books singleton * Removed singletons for level records - removed loader calls in d2app - changed type references from d2datadict to d2records - added a `MapGenerator` in d2mapgen which uses thew asset manager and map engine - package-level map generation functions are now MapGenerator methods - `d2datadict.GetLevelDetails(id int)` is now a method of the RecordManager * remove SkillCalc and MissileCalc singletons * Removed CharStats and ItemStatCost singletons - added an ItemStatFactory which uses the asset manager to create stats - package-level functions for stats in d2item are now StatFactory methods - changed type references from d2datadict to d2records - `d2player.GetAllPlayerStates` is now a method of the `PlayerStateFactory` * Removed DkillDesc and Skills singletons from d2datadict - removed loader calls from d2app - diablo2stats.Stat instances are given a reference to the factory for doing record lookups * update the stats test to use mock a asset manager and stat factory * fixed diablo2stats tests and diablo2item tests * removed CompCodes singleton from d2datadict * remove cubemain singleton from d2datadict * removed DifficultyLevels singleton from d2datadict * removed ElemTypes singleton from d2datadict * removed events.go loader from d2datadict (was unused) * removed Gems singleton from d2datadict * removed Hireling and Inventory singletons from d2datadict * removed MagicPrefix and MagicSuffix singletons from d2datadict * removed ItemRatios singleton from d2datadict * removed Missiles singleton from d2datadict * removed MonModes singleton * Removed all monster and npc singletons from d2datadict - MapStamp instances now get a reference to their factory for doing record lookups * removed SoundEntry and SoundEnviron singletons from d2datadict
2020-09-20 17:52:01 -04:00
monstatRecord *d2records.MonStatsRecord
monstatEx *d2records.MonStats2Record
HasPaths bool
isDone bool
2019-11-10 03:36:53 -05:00
}
const (
magicOffsetX = 5
magicOffsetScalarX = 8
magicOffsetScalarY = 16
minAnimationRepetitions = 3
maxAnimationRepetitions = 5
)
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
return slice[rand.Intn(len(slice))]
}
return ""
}
// ID returns the NPC uuid
func (v *NPC) ID() string {
return v.mapEntity.uuid
}
// Render renders this entity's animated composite.
func (v *NPC) Render(target d2interface.Surface) {
renderOffset := v.Position.RenderOffset()
target.PushTranslation(
int((renderOffset.X()-renderOffset.Y())*magicOffsetScalarY),
int(((renderOffset.X()+renderOffset.Y())*magicOffsetScalarX)-magicOffsetX),
)
defer target.Pop()
if v.composite.Render(target) != nil {
return
}
}
// Path returns the current part of the entity's path.
func (v *NPC) Path() d2path.Path {
return v.Paths[v.path]
}
// NextPath returns the next part of the entity's path.
func (v *NPC) NextPath() d2path.Path {
v.path++
if v.path == len(v.Paths) {
v.path = 0
}
return v.Paths[v.path]
}
// 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.
func (v *NPC) SetPaths(paths []d2path.Path) {
v.Paths = paths
v.HasPaths = len(paths) > 0
v.isDone = true
}
// Advance is called once per frame and processes a
// single game tick.
func (v *NPC) Advance(tickTime float64) {
v.Step(tickTime)
if err := v.composite.Advance(tickTime); err != nil {
return
}
if v.HasPaths && v.wait() {
// If at the target, set target to the next path.
v.isDone = false
path := v.NextPath()
v.setTarget(
path.Position,
v.next,
)
v.action = path.Action
}
}
// 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
}
func (v *NPC) next() {
var newAnimationMode d2enum.MonsterAnimationMode
v.isDone = true
2020-10-26 02:38:15 -04:00
// nolint:gosec // not concerned with crypto-strong randomness
v.repetitions = minAnimationRepetitions + rand.Intn(maxAnimationRepetitions)
switch d2enum.NPCActionType(v.action) {
case d2enum.NPCActionSkill1:
newAnimationMode = d2enum.MonsterAnimationModeSkill1
v.repetitions = 0
case d2enum.NPCActionInvalid, d2enum.NPCAction1, d2enum.NPCAction2, d2enum.NPCAction3:
newAnimationMode = d2enum.MonsterAnimationModeNeutral
v.repetitions = 0
default:
newAnimationMode = d2enum.MonsterAnimationModeNeutral
v.repetitions = 0
}
if v.composite.GetAnimationMode() != newAnimationMode.String() {
if err := v.composite.SetMode(newAnimationMode, v.composite.GetWeaponClass()); err != nil {
return
}
}
}
// rotate sets direction and changes animation
func (v *NPC) rotate(direction int) {
var newMode d2enum.MonsterAnimationMode
if !v.atTarget() {
newMode = d2enum.MonsterAnimationModeWalk
} else {
newMode = d2enum.MonsterAnimationModeNeutral
}
if newMode.String() != v.composite.GetAnimationMode() {
if err := v.composite.SetMode(newMode, v.composite.GetWeaponClass()); err != nil {
return
}
}
if v.composite.GetDirection() != direction {
v.composite.SetDirection(direction)
}
}
// Selectable returns true if the object can be highlighted/selected.
func (v *NPC) Selectable() bool {
// is there something handy that determines selectable npc's?
return v.name != ""
}
// 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 {
return v.name
}
// 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
}
// GetSize returns the current frame size
2020-09-12 16:25:09 -04:00
func (v *NPC) GetSize() (width, height int) {
return v.composite.GetSize()
}