2020-07-31 17:55:11 -04:00
package d2mapentity
import (
"fmt"
2020-09-12 16:25:09 -04:00
2020-09-23 13:30:15 -04:00
"github.com/google/uuid"
2020-09-12 16:51:30 -04:00
2020-07-31 17:55:11 -04:00
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
2020-09-08 15:58:35 -04:00
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
2020-07-31 17:55:11 -04:00
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
2020-09-20 17:52:01 -04:00
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
2020-07-31 17:55:11 -04:00
"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"
2020-09-23 13:30:15 -04:00
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
2020-07-31 17:55:11 -04:00
)
2020-09-12 16:51:30 -04:00
// NewMapEntityFactory creates a MapEntityFactory instance with the given asset manager
2020-09-20 17:52:01 -04:00
func NewMapEntityFactory ( asset * d2asset . AssetManager ) ( * MapEntityFactory , error ) {
itemFactory , err := diablo2item . NewItemFactory ( asset )
if err != nil {
return nil , err
}
stateFactory , err := d2hero . NewHeroStateFactory ( asset )
if err != nil {
return nil , err
}
entityFactory := & MapEntityFactory {
stateFactory ,
asset ,
itemFactory ,
}
return entityFactory , nil
2020-09-12 16:51:30 -04:00
}
// MapEntityFactory creates map entities for the MapEngine
type MapEntityFactory struct {
2020-09-20 17:52:01 -04:00
* d2hero . HeroStateFactory
2020-09-12 16:51:30 -04:00
asset * d2asset . AssetManager
2020-09-20 17:52:01 -04:00
item * diablo2item . ItemFactory
2020-09-12 16:51:30 -04:00
}
2020-07-31 17:55:11 -04:00
// 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.
2020-09-12 16:51:30 -04:00
func ( f * MapEntityFactory ) NewPlayer ( id , name string , x , y , direction int , heroType d2enum . Hero ,
2020-09-20 17:52:01 -04:00
stats * d2hero . HeroStatsState , skills map [ int ] * d2hero . HeroSkill , equipment * d2inventory . CharacterEquipment ) * Player {
2020-07-31 17:55:11 -04:00
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 ( ) ,
}
2020-09-12 16:51:30 -04:00
composite , err := f . asset . LoadComposite ( d2enum . ObjectTypePlayer , heroType . GetToken ( ) ,
2020-07-31 17:55:11 -04:00
d2resource . PaletteUnits )
if err != nil {
panic ( err )
}
2020-09-20 17:52:01 -04:00
stats . NextLevelExp = f . asset . Records . GetExperienceBreakpoint ( heroType , stats . Level )
2020-10-21 23:25:53 -04:00
stats . Stamina = float64 ( stats . MaxStamina )
2020-07-31 17:55:11 -04:00
2020-09-20 17:52:01 -04:00
defaultCharStats := f . asset . Records . Character . Stats [ heroType ]
statsState := f . HeroStateFactory . CreateHeroStatsState ( heroType , defaultCharStats )
heroState , _ := f . CreateHeroState ( name , heroType , statsState )
2020-09-20 11:55:44 -04:00
attackSkillID := 0
2020-07-31 17:55:11 -04:00
result := & Player {
mapEntity : newMapEntity ( x , y ) ,
composite : composite ,
Equipment : equipment ,
2020-09-20 17:52:01 -04:00
Stats : heroState . Stats ,
Skills : heroState . Skills ,
2020-09-20 11:55:44 -04:00
//TODO: active left & right skill should be loaded from save file instead
2020-09-20 17:52:01 -04:00
LeftSkill : heroState . Skills [ attackSkillID ] ,
RightSkill : heroState . Skills [ attackSkillID ] ,
name : name ,
Class : heroType ,
2020-08-06 10:30:23 -04:00
//nameLabel: d2ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteStatic),
2020-10-07 16:12:56 -04:00
isRunToggled : false ,
2020-07-31 17:55:11 -04:00
isInTown : true ,
2020-10-07 16:12:56 -04:00
isRunning : false ,
2020-07-31 17:55:11 -04:00
}
2020-08-05 21:27:45 -04:00
result . mapEntity . uuid = id
2020-07-31 17:55:11 -04:00
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.
2020-09-20 17:52:01 -04:00
func ( f * MapEntityFactory ) NewMissile ( x , y int , record * d2records . MissileRecord ) ( * Missile , error ) {
2020-09-12 16:51:30 -04:00
animation , err := f . asset . LoadAnimation (
2020-07-31 17:55:11 -04:00
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
2020-09-12 16:51:30 -04:00
func ( f * MapEntityFactory ) NewItem ( x , y int , codes ... string ) ( * Item , error ) {
2020-09-20 17:52:01 -04:00
item , err := f . item . NewItem ( codes ... )
2020-07-31 17:55:11 -04:00
2020-09-20 17:52:01 -04:00
if err != nil {
return nil , err
2020-07-31 17:55:11 -04:00
}
filename := item . CommonRecord ( ) . FlippyFile
filepath := fmt . Sprintf ( "%s/%s.DC6" , d2resource . ItemGraphics , filename )
2020-09-12 16:51:30 -04:00
animation , err := f . asset . LoadAnimation ( filepath , d2resource . PaletteUnits )
2020-07-31 17:55:11 -04:00
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.
2020-09-20 17:52:01 -04:00
func ( f * MapEntityFactory ) NewNPC ( x , y int , monstat * d2records . MonStatsRecord , direction int ) ( * NPC , error ) {
2020-07-31 17:55:11 -04:00
result := & NPC {
mapEntity : newMapEntity ( x , y ) ,
HasPaths : false ,
monstatRecord : monstat ,
2020-09-20 17:52:01 -04:00
monstatEx : f . asset . Records . Monster . Stats2 [ monstat . ExtraDataKey ] ,
2020-07-31 17:55:11 -04:00
}
var equipment [ 16 ] string
for compType , opts := range result . monstatEx . EquipmentOptions {
equipment [ compType ] = selectEquip ( opts )
}
2020-09-23 13:30:54 -04:00
composite , err := f . asset . LoadComposite ( d2enum . ObjectTypeCharacter , monstat . AnimationDirectoryToken ,
2020-07-31 17:55:11 -04:00
d2resource . PaletteUnits )
2020-10-22 01:12:06 -04:00
2020-09-23 13:30:54 -04:00
if err != nil {
return nil , err
}
2020-10-22 01:12:06 -04:00
2020-07-31 17:55:11 -04:00
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 {
2020-09-08 15:58:35 -04:00
result . name = d2tbl . TranslateString ( result . monstatRecord . NameString )
2020-07-31 17:55:11 -04:00
}
return result , nil
}
2020-09-12 16:51:30 -04:00
2020-10-10 18:47:51 -04:00
// NewCastOverlay creates a cast overlay map entity
func ( f * MapEntityFactory ) NewCastOverlay ( x , y int , overlayRecord * d2records . OverlayRecord ) ( * CastOverlay , error ) {
animation , err := f . asset . LoadAnimationWithEffect (
fmt . Sprintf ( "/data/Global/Overlays/%s.dcc" , overlayRecord . Filename ) ,
d2resource . PaletteUnits ,
d2enum . DrawEffectModulate ,
)
// TODO: Frame index and played count seem to be shared across the cloned animation objects when we retrieve the animation from the asset manager cache.
animation . Rewind ( )
animation . ResetPlayedCount ( )
if err != nil {
return nil , err
}
animationSpeed := float64 ( overlayRecord . AnimRate * 25.0 ) / 1000.0
playLoop := false // TODO: should be based on the overlay record, some overlays can repeat(e.g. Bone Shield, Frozen Armor)
animation . SetPlayLength ( animationSpeed )
animation . SetPlayLoop ( playLoop )
animation . PlayForward ( )
targetX := x + overlayRecord . XOffset
targetY := y + overlayRecord . YOffset
entity := NewAnimatedEntity ( targetX , targetY , animation )
result := & CastOverlay {
AnimatedEntity : entity ,
record : overlayRecord ,
2020-10-21 23:25:53 -04:00
playLoop : playLoop ,
2020-10-10 18:47:51 -04:00
}
return result , nil
}
2020-09-12 16:51:30 -04:00
// NewObject creates an instance of AnimatedComposite
2020-09-20 20:30:27 -04:00
func ( f * MapEntityFactory ) NewObject ( x , y int , objectRec * d2records . ObjectDetailsRecord ,
2020-09-12 16:51:30 -04:00
palettePath string ) ( * Object , error ) {
locX , locY := float64 ( x ) , float64 ( y )
entity := & Object {
2020-09-23 13:30:15 -04:00
uuid : uuid . New ( ) . String ( ) ,
2020-09-12 16:51:30 -04:00
objectRecord : objectRec ,
Position : d2vector . NewPosition ( locX , locY ) ,
name : d2tbl . TranslateString ( objectRec . Name ) ,
}
2020-09-20 20:30:27 -04:00
objectType := f . asset . Records . Object . Types [ objectRec . Index ]
2020-09-12 16:51:30 -04:00
composite , err := f . asset . LoadComposite ( d2enum . ObjectTypeItem , objectType . Token ,
palettePath )
if err != nil {
return nil , err
}
entity . composite = composite
2020-09-23 13:30:54 -04:00
err = entity . setMode ( d2enum . ObjectAnimationModeNeutral , 0 , false )
if err != nil {
return nil , err
}
2020-09-12 16:51:30 -04:00
2020-09-23 13:30:54 -04:00
_ , err = initObject ( entity )
if err != nil {
return nil , err
}
2020-09-12 16:51:30 -04:00
return entity , nil
}