mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-04 15:46:51 -05: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:
parent
9789372e8f
commit
78ecc3557e
@ -210,6 +210,7 @@ const (
|
||||
AnimationData = "/data/global/animdata.d2"
|
||||
PlayerAnimationBase = "/data/global/CHARS"
|
||||
MissileData = "/data/global/missiles"
|
||||
ItemGraphics = "/data/global/items"
|
||||
|
||||
// --- Inventory Data ---
|
||||
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
56
d2core/d2map/d2mapentity/item.go
Normal file
56
d2core/d2map/d2mapentity/item.go
Normal 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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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]
|
||||
|
@ -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]
|
||||
|
25
d2networking/d2netpacket/packet_item_spawn.go
Normal file
25
d2networking/d2netpacket/packet_item_spawn.go
Normal 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,
|
||||
},
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user