1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-20 06:05:23 +00:00

simple item spawning in map (#651)

* wip d2items system and item properties

* added loader for TreasureClassEx.txt

* wip item spawn from treasure class records

* wip items

* add call to init item equivalencies, remove treasure class test from d2app

* made item affix records global var a map of affix codes to the records

* changed how item to item common record equivalency is determined

* changed set items records export to a map of their codes to the records, grouped property params into a struct

* changed property parameter field from calcstring to string

* fixed bug in stat value clone

* adding equipper interface as part of stat context, eventually to be used to resolve set bonus (among other things)

* made the item interface simpler, only needs name and description methods

* adding equipper interface, for anything that will equip or have active items

* handle case where min and max are swapped, removed commented code

* added property/stat resolution for magic, rare, set, and unique items

* adding item generator which can roll for items using treasure class records

* fixed item equivalency func being called in the wrong spot

* added item spawning

- added packet type for spawning items
- added client/server handlers for SpawnItem packets
- added map entity for items
- added simpler item provider function in diablo2item package
- added debug terminal command for spawning items
This commit is contained in:
lord 2020-07-30 12:04:05 -07:00 committed by GitHub
parent 9789372e8f
commit 78ecc3557e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 279 additions and 10 deletions

View File

@ -210,6 +210,7 @@ const (
AnimationData = "/data/global/animdata.d2"
PlayerAnimationBase = "/data/global/CHARS"
MissileData = "/data/global/missiles"
ItemGraphics = "/data/global/items"
// --- Inventory Data ---

View File

@ -2,6 +2,81 @@ package diablo2item
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
func NewItem(codes ...string) *Item {
var item *Item
var common, set, unique string
var prefixes, suffixes []string
for _, code := range codes {
if found := d2datadict.CommonItems[code]; found != nil {
common = code
continue
}
if found := d2datadict.SetItems[code]; found != nil {
set = code
continue
}
if found := d2datadict.UniqueItems[code]; found != nil {
unique = code
continue
}
if found := d2datadict.MagicPrefix[code]; found != nil {
if prefixes == nil {
prefixes = make([]string, 0)
}
prefixes = append(prefixes, code)
continue
}
if found := d2datadict.MagicSuffix[code]; found != nil {
if suffixes == nil {
suffixes = make([]string, 0)
}
suffixes = append(suffixes, code)
continue
}
}
if common != "" { // we will at least have a regular item
item = &Item{CommonCode: common}
if set != "" { // it's a set item
item.SetItemCode = set
return item.init()
}
if unique != "" { // it's a unique item
item.UniqueCode = unique
return item.init()
}
if prefixes != nil {
if len(prefixes) > 0 { // it's a magic or rare item
item.PrefixCodes = prefixes
}
}
if suffixes != nil {
if len(suffixes) > 0 { // it's a magic or rare item
item.SuffixCodes = suffixes
}
}
return item.init()
}
return nil
}
// NewProperty creates a property
func NewProperty(code string, values ...int) *Property {
record := d2datadict.Properties[code]

View File

@ -165,7 +165,7 @@ func (i *Item) PrefixRecords() []*d2datadict.ItemAffixCommonRecord {
return affixRecords(i.PrefixCodes, d2datadict.MagicPrefix)
}
// PrefixRecords returns the ItemAffixCommonRecords of the prefixes of the item
// SuffixRecords returns the ItemAffixCommonRecords of the prefixes of the item
func (i *Item) SuffixRecords() []*d2datadict.ItemAffixCommonRecord {
return affixRecords(i.SuffixCodes, d2datadict.MagicSuffix)
}
@ -345,6 +345,25 @@ func (i *Item) pickMagicSuffixes(max, totalMax int) {
}
}
// SetSeed sets the item generator seed
func (i *Item) SetSeed(seed int64) {
if i.rand == nil {
source := rand.NewSource(seed)
i.rand = rand.New(source)
}
i.Seed = seed
}
func (i *Item) init() *Item {
if i.rand == nil {
i.SetSeed(0)
}
i.generateAllProperties()
i.updateItemAttributes()
return i
}
func (i *Item) generateAllProperties() {
if i.attributes == nil {
i.attributes = &itemAttributes{}
@ -452,12 +471,12 @@ func (i *Item) updateItemAttributes() {
}
def, minDef, maxDef := 0, r.MinAC, r.MaxAC
if maxDef < minDef {
minDef, maxDef = maxDef, minDef
}
if minDef < 1 && maxDef < 1 {
if maxDef < minDef {
minDef, maxDef = maxDef, minDef
}
if minDef > 1 && maxDef > 1 {
def = i.rand.Intn(maxDef-minDef+1) + minDef
}

View File

@ -153,8 +153,7 @@ func (ig *ItemGenerator) ItemsFromTreasureClass(tcr *d2datadict.TreasureClassRec
itemSlice := ig.ItemsFromTreasureClass(record)
for itemIdx := range itemSlice {
itemSlice[itemIdx].applyDropModifier(ig.rollDropModifier(tcr))
itemSlice[itemIdx].generateAllProperties()
itemSlice[itemIdx].updateItemAttributes()
itemSlice[itemIdx].init()
result = append(result, itemSlice[itemIdx])
}
} else {
@ -162,8 +161,7 @@ func (ig *ItemGenerator) ItemsFromTreasureClass(tcr *d2datadict.TreasureClassRec
item := ig.ItemFromTreasure(picked)
if item != nil {
item.applyDropModifier(ig.rollDropModifier(tcr))
item.generateAllProperties()
item.updateItemAttributes()
item.init()
result = append(result, item)
}
}

View File

@ -0,0 +1,56 @@
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"
)
const (
errInvalidItemCodes = "invalid item codes supplied"
)
type Item struct {
*AnimatedEntity
Item *diablo2item.Item
}
func (i *Item) GetPosition() d2vector.Position {
return i.AnimatedEntity.Position
}
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
}

View File

@ -24,6 +24,7 @@ const (
moveErrStr = "failed to send MovePlayer packet to the server, playerId: %s, x: %g, x: %g\n"
bindControlsErrStr = "failed to add gameControls as input handler for player: %s\n"
castErrStr = "failed to send CastSkill packet to the server, playerId: %s, missileId: %d, x: %g, x: %g\n"
spawnItemErrStr = "failed to send SpawnItem packet to the server: (%d, %d) %+v"
)
// Game represents the Gameplay screen
@ -87,6 +88,24 @@ func CreateGame(
// OnLoad loads the resources for the Gameplay screen
func (v *Game) OnLoad(_ d2screen.LoadingState) {
v.audioProvider.PlayBGM("")
v.terminal.BindAction(
"spawnitem",
"spawns an item at the local player position",
func(code1, code2, code3, code4, code5 string) {
codes := []string{code1, code2, code3, code4, code5}
v.debugSpawnItemAtPlayer(codes...)
},
)
v.terminal.BindAction(
"spawnitemat",
"spawns an item at the x,y coordinates",
func(x, y int, code1, code2, code3, code4, code5 string) {
codes := []string{code1, code2, code3, code4, code5}
v.debugSpawnItemAtLocation(x, y, codes...)
},
)
}
// OnUnload releases the resources of Gameplay screen
@ -99,6 +118,9 @@ func (v *Game) OnUnload() error {
return err
}
v.terminal.UnbindAction("spawnItemAt")
v.terminal.UnbindAction("spawnItem")
if err := v.gameClient.Close(); err != nil {
return err
}
@ -231,3 +253,23 @@ func (v *Game) OnPlayerCast(missileID int, targetX, targetY float64) {
fmt.Printf(castErrStr, v.gameClient.PlayerID, missileID, targetX, targetY)
}
}
func (v *Game) debugSpawnItemAtPlayer(codes ...string) {
if v.localPlayer == nil {
return
}
pos := v.localPlayer.GetPosition()
tile := pos.Tile()
x, y := int(tile.X()), int(tile.Y())
v.debugSpawnItemAtLocation(x, y, codes...)
}
func (v *Game) debugSpawnItemAtLocation(x, y int, codes ...string) {
packet := d2netpacket.CreateSpawnItemPacket(x, y, codes...)
err := v.gameClient.SendPacketToServer(packet)
if err != nil {
fmt.Printf(spawnItemErrStr, x, y, codes)
}
}

View File

@ -116,6 +116,10 @@ func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error {
if err := g.handleCastSkillPacket(packet); err != nil {
return err
}
case d2netpackettype.SpawnItem:
if err := g.handleSpawnItemPacket(packet); err != nil {
return err
}
case d2netpackettype.Ping:
if err := g.handlePingPacket(); err != nil {
log.Printf("GameClient: error responding to server ping: %s", err)
@ -173,6 +177,17 @@ func (g *GameClient) handleAddPlayerPacket(packet d2netpacket.NetPacket) error {
return nil
}
func (g *GameClient) handleSpawnItemPacket(packet d2netpacket.NetPacket) error {
item := packet.PacketData.(d2netpacket.SpawnItemPacket)
itemEntity, err := d2mapentity.CreateItem(item.X, item.Y, item.Codes...)
if err == nil {
g.MapEngine.AddEntity(itemEntity)
}
return err
}
func (g *GameClient) handleMovePlayerPacket(packet d2netpacket.NetPacket) error {
movePlayer := packet.PacketData.(d2netpacket.MovePlayerPacket)
player := g.Players[movePlayer.PlayerID]

View File

@ -24,6 +24,7 @@ const (
Pong // Responds to a Ping packet
ServerClosed // Sent by the local host when it has closed the server
CastSkill // Sent by client or server, indicates entity casting skill
SpawnItem // Sent by server
)
func (n NetPacketType) String() string {
@ -38,6 +39,7 @@ func (n NetPacketType) String() string {
Pong: "Pong",
ServerClosed: "ServerClosed",
CastSkill: "CastSkill",
SpawnItem: "SpawnItem",
}
return strings[n]

View File

@ -0,0 +1,25 @@
package d2netpacket
import (
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
)
// CreateItemPacket contains the data required to create a Item entity
type SpawnItemPacket struct {
X int `json:"x"`
Y int `json:"y"`
Codes []string `json:"codes"`
}
// CreateSpawnItemPacket returns a NetPacket which declares a
// SpawnItemPacket with the data in given parameters.
func CreateSpawnItemPacket(x, y int, codes ...string) NetPacket {
return NetPacket{
PacketType: d2netpackettype.SpawnItem,
PacketData: SpawnItemPacket{
X: x,
Y: y,
Codes: codes,
},
}
}

View File

@ -151,6 +151,10 @@ func runNetworkServer() {
if err := handleMovePlayer(packetType, stringData); err != nil {
log.Printf("GameServer error: %v", err)
}
case d2netpackettype.SpawnItem:
if err := handleSpawnItem(packetType, stringData); err != nil {
log.Printf("GameServer error: %v", err)
}
case d2netpackettype.Pong:
if err := handlePingPong(stringData); err != nil {
log.Printf("GameServer error: %v", err)
@ -206,6 +210,30 @@ func handleMovePlayer(packetType d2netpackettype.NetPacketType, stringData strin
return nil
}
func handleSpawnItem(packetType d2netpackettype.NetPacketType, stringData string) error {
packetData := d2netpacket.SpawnItemPacket{}
err := json.Unmarshal([]byte(stringData), &packetData)
if err != nil {
log.Printf("GameServer: error unmarshalling %T: %s", packetData, err)
return err
}
netPacket := d2netpacket.NetPacket{
PacketType: packetType,
PacketData: packetData,
}
for _, player := range singletonServer.clientConnections {
err = player.SendPacketToClient(netPacket)
if err != nil {
log.Printf("GameServer: error sending %T to client %s: %s", packetData, player.GetUniqueID(), err)
}
}
return nil
}
func handlePingPong(stringData string) error {
packetData := d2netpacket.PlayerConnectionRequestPacket{}
err := json.Unmarshal([]byte(stringData), &packetData)
@ -377,7 +405,15 @@ func OnPacketReceived(client ClientConnection, packet d2netpacket.NetPacket) err
log.Printf("GameServer: error sending %T to client %s: %s", packet, player.GetUniqueID(), err)
}
}
case d2netpackettype.SpawnItem:
for _, player := range singletonServer.clientConnections {
err := player.SendPacketToClient(packet)
if err != nil {
log.Printf("GameServer: error sending %T to client %s: %s", packet, player.GetUniqueID(), err)
}
}
}
return nil
}