eq2go/internal/spawn/spawn.go

1157 lines
30 KiB
Go

package spawn
import (
"math"
"sync"
"sync/atomic"
"time"
"eq2emu/internal/common"
)
// Damage packet constants
const (
DamagePacketTypeSiphonSpell = 0x41
DamagePacketTypeSiphonSpell2 = 0x49
DamagePacketTypeMultipleDamage = 0x80
DamagePacketTypeSimpleDamage = 0xC0
DamagePacketTypeSpellDamage = 0xC1
DamagePacketTypeSimpleCritDmg = 0xC4
DamagePacketTypeSpellCritDmg = 0xC5
DamagePacketTypeSpellDamage2 = 0xC8
DamagePacketTypeSpellDamage3 = 0xC9
DamagePacketTypeRangeDamage = 0xE2
DamagePacketTypeRangeSpellDmg = 0xE3
DamagePacketTypeRangeSpellDmg2 = 0xEA
)
// Damage packet results
const (
DamagePacketResultNoDamage = 0
DamagePacketResultSuccessful = 1
DamagePacketResultMiss = 4
DamagePacketResultDodge = 8
DamagePacketResultParry = 12
DamagePacketResultRiposte = 16
DamagePacketResultBlock = 20
DamagePacketResultDeathBlow = 24
DamagePacketResultInvulnerable = 28
DamagePacketResultResist = 36
DamagePacketResultReflect = 40
DamagePacketResultImmune = 44
DamagePacketResultDeflect = 48
DamagePacketResultCounter = 52
DamagePacketResultFocus = 56
DamagePacketResultCounterStrike = 60
DamagePacketResultBash = 64
)
// Damage types
const (
DamagePacketDamageTypeSlash = 0
DamagePacketDamageTypeCrush = 1
DamagePacketDamageTypePierce = 2
DamagePacketDamageTypeHeat = 3
DamagePacketDamageTypeCold = 4
DamagePacketDamageTypeMagic = 5
DamagePacketDamageTypeMental = 6
DamagePacketDamageTypeDivine = 7
DamagePacketDamageTypeDisease = 8
DamagePacketDamageTypePoison = 9
DamagePacketDamageTypeDrown = 10
DamagePacketDamageTypeFalling = 11
DamagePacketDamageTypePain = 12
DamagePacketDamageTypeHit = 13
DamagePacketDamageTypeFocus = 14
)
// Activity status flags
const (
ActivityStatusRoleplaying = 1
ActivityStatusAnonymous = 2
ActivityStatusLinkdead = 4
ActivityStatusCamping = 8
ActivityStatusLFG = 16
ActivityStatusLFW = 32
ActivityStatusSolid = 64
ActivityStatusImmunityGained = 8192
ActivityStatusImmunityRemaining = 16384
ActivityStatusAFK = 32768
)
// Position states
const (
PosStateKneeling = 64
PosStateSolid = 128
PosStateNotargetCursor = 256
PosStateCrouching = 512
)
// Encounter states
const (
EncounterStateNone = 0
EncounterStateAvailable = 1
EncounterStateBroken = 2
EncounterStateLocked = 3
EncounterStateOvermatched = 4
EncounterStateNoReward = 5
)
// Loot methods and modes
type GroupLootMethod int
const (
MethodLeader GroupLootMethod = 0
MethodFFA GroupLootMethod = 1
MethodLotto GroupLootMethod = 2
MethodNeedBeforeGreed GroupLootMethod = 3
MethodRoundRobin GroupLootMethod = 4
)
type AutoLootMode int
const (
AutoLootDisabled = 0
AutoLootAccept = 1
AutoLootDecline = 2
)
type LootTier int
const (
ItemsAll LootTier = 0
ItemsTreasuredPlus LootTier = 1
ItemsLegendaryPlus LootTier = 2
ItemsFabledPlus LootTier = 3
)
// SpawnProximityType defines types of spawn proximity tracking
type SpawnProximityType int
const (
SpawnProximityDatabaseID = 0
SpawnProximityLocationID = 1
)
// BasicInfoStruct contains basic spawn statistics
type BasicInfoStruct struct {
CurHP int32
MaxHP int32
HPBase int32
HPBaseInstance int32
CurPower int32
MaxPower int32
PowerBase int32
PowerBaseInstance int32
CurSavagery int32
MaxSavagery int32
SavageryBase int32
CurDissonance int32
MaxDissonance int32
DissonanceBase int32
AssignedAA int16
UnassignedAA int16
TradeskillAA int16
UnassignedTradeskillAA int16
PrestigeAA int16
UnassignedPrestigeAA int16
TradeskillPrestigeAA int16
UnassignedTradeskillPrestigeAA int16
AAXPRewards int32
}
// MovementLocation represents a point in a movement path
type MovementLocation struct {
X float32
Y float32
Z float32
Speed float32
Attackable bool
LuaFunction string
Mapped bool
GridID int32
Stage int8
ResetHPOnRunback bool
UseNavPath bool
}
// MovementData contains movement configuration
type MovementData struct {
X float32
Y float32
Z float32
Speed float32
Delay int32
LuaFunction string
Heading float32
UseMovementLocationHeading bool
UseNavPath bool
}
// SpawnUpdate tracks what aspects of a spawn need updating
type SpawnUpdate struct {
SpawnID int32
InfoChanged bool
VisChanged bool
PosChanged bool
// TODO: Add Client reference when client system is implemented
// Client *Client
}
// SpawnData contains spawn packet data
type SpawnData struct {
// TODO: Add Spawn reference when implemented
// Spawn *Spawn
Data []byte
Size int32
}
// TimedGridData tracks movement through grid system
type TimedGridData struct {
Timestamp int32
GridID int32
X float32
Y float32
Z float32
OffsetY float32
ZoneGroundY float32
NPCSave bool
WidgetID int32
}
// EntityCommand represents an interactable command
type EntityCommand struct {
Name string
Distance float32
Command string
ErrorText string
CastTime int16
SpellVisual int32
AllowOrDeny map[int32]bool // player IDs and whether they're allowed
DefaultAllowList bool // if false, it's a deny list
}
// SpawnProximity tracks proximity-based events
type SpawnProximity struct {
X float32
Y float32
Z float32
SpawnValue int32
SpawnType int8
Distance float32
InRangeLuaFunction string
LeavingRangeLuaFunction string
SpawnsInProximity map[int32]bool
}
var nextSpawnID int32 = 1
// NextID generates the next unique spawn ID
// Handles special cases like avoiding IDs ending in 255 and wraparound
func NextID() int32 {
for {
id := atomic.AddInt32(&nextSpawnID, 1)
// Handle wraparound
if id == 0xFFFFFFFE {
atomic.StoreInt32(&nextSpawnID, 1)
continue
}
// Avoid IDs ending in 255 to prevent client confusion/crashes
if (id-255)%256 == 0 {
continue
}
return id
}
}
// Spawn represents a game entity that can appear in the world
type Spawn struct {
// Basic identification
id int32
databaseID int32
// Appearance and positioning
appearance common.AppearanceData
size int16
sizeOffset int8
// State flags (using atomic for thread safety)
changed atomic.Bool
positionChanged atomic.Bool
infoChanged atomic.Bool
visChanged atomic.Bool
isRunning atomic.Bool
sizeChanged atomic.Bool
following atomic.Bool
isAlive atomic.Bool
deletedSpawn atomic.Bool
resetMovement atomic.Bool
knockedBack atomic.Bool
// Game state
basicInfo BasicInfoStruct
factionID int32
spawnType int8
target int32
lastAttacker int32
// Commands and interaction
primaryCommandListID int32
secondaryCommandListID int32
primaryCommandList []*EntityCommand
secondaryCommandList []*EntityCommand
// Movement and positioning
lastMovementUpdate int32
lastLocationUpdate int32
lastGridUpdate int32
forceMapCheck bool
movementLocations []*MovementLocation
movementLoop []*MovementData
movementIndex int16
runningTo int32
invulnerable bool
// Group and spawn relationships
groupID int32
spawnGroupList []*Spawn
// Location and respawn data
spawnLocationID int32
spawnEntryID int32
spawnLocationSpawnsID int32
respawn int32
respawnOffsetLow int32
respawnOffsetHigh int32
duplicatedSpawn bool
expireTime int32
expireOffset int32
xOffset float32
yOffset float32
zOffset float32
deviation int32
// Loot system
lootItems []*Item // TODO: Define Item type
lootCoins int32
lootGroupID int32
lootMethod GroupLootMethod
lootRarity int8
looterSpawnID int32
lootComplete map[int32]bool
isLootComplete bool
isLootDispensed bool
lootName string
lootTier int32
lootDropType int32
trapTriggered bool
trapState int32
chestDropTime int32
trapOpenedTime int32
// Merchant data
merchantID int32
merchantType int8
merchantMinLevel int32
merchantMaxLevel int32
isCollector bool
// Transportation
transporterID int32
isTransportSpawn bool
railID int64
railPassengers map[int32]bool
// Scripting and AI
spawnScript string
spawnScriptSetDB bool
questIDs []int32
// Quest and access requirements
hasQuestsRequired bool
hasHistoryRequired bool
reqQuestsPrivate bool
reqQuestsOverride int16
reqQuestsContinuedAccess bool
requiredQuests map[int32][]int16
// TODO: Add LUAHistory type
// requiredHistory map[int32]*LUAHistory
allowedAccess map[int32]int8
// Visual and display state
tmpVisualState int
tmpActionState int
illusionModel int16
// Items and pickups
pickupItemID int32
pickupUniqueItemID int32
houseCharacterID int32
// Proximity tracking
hasSpawnProximities bool
spawnProximities []*SpawnProximity
// Creature flags
isFlyingCreature bool
isWaterCreature bool
isPet bool
scaredByStrongPlayers bool
// Following system
followTarget int32
followDistance int32
// Temporary variables (for Lua scripting)
tempVariableTypes map[string]int8
tempVariables map[string]string
tempVariableSpawn map[string]int32
// TODO: Add other temp variable types when systems are implemented
// tempVariableZone map[string]*ZoneServer
// tempVariableItem map[string]*Item
// tempVariableQuest map[string]*Quest
// Animation and timing
spawnAnim int32
addedToWorldTimestamp int32
spawnAnimLeeway int16
lastHeadingAngle float32
// Region and mapping
// TODO: Add RegionMap and Map types
// regionMap *RegionMap
// currentMap *Map
// regions map[map[*RegionNode]*ZBSPNode]*RegionStatus
establishedGridID map[int32]*TimedGridData
ignoredWidgets map[int32]bool
triggerWidgetID int32
// Equipment
// TODO: Add EquipmentItemList type
// equipmentList *EquipmentItemList
// appearanceEquipmentList *EquipmentItemList
// Zone reference
// TODO: Add ZoneServer reference when implemented
// zone *ZoneServer
// Knockback physics
knockedBackTimeStep float32
knockedBackHDistance float32
knockedBackVDistance float32
knockedBackDuration float32
knockedBackStartX float32
knockedBackStartY float32
knockedBackStartZ float32
knockedBackEndTime int32
knockedAngle float32
// TODO: Add glm::vec3 equivalent
// knockedVelocity Vec3
// Database omission flag
isOmittedByDBFlag bool
// Sound settings
disableSounds bool
// Thread safety
updateMutex sync.RWMutex
spawnMutex sync.RWMutex
lootItemsMutex sync.RWMutex
commandMutex sync.Mutex
regionMutex sync.Mutex
railMutex sync.Mutex
gridMutex sync.Mutex
movementMutex sync.RWMutex
requiredQuestsMutex sync.RWMutex
requiredHistoryMutex sync.RWMutex
ignoredWidgetsMutex sync.RWMutex
}
// NewSpawn creates a new spawn instance with default values
// Initializes all fields to appropriate defaults matching the C++ constructor
func NewSpawn() *Spawn {
s := &Spawn{
id: NextID(),
size: 32,
sizeOffset: 0,
factionID: 0,
spawnType: 0,
target: 0,
lastAttacker: 0,
primaryCommandListID: 0,
secondaryCommandListID: 0,
primaryCommandList: make([]*EntityCommand, 0),
secondaryCommandList: make([]*EntityCommand, 0),
groupID: 0,
spawnLocationID: 0,
spawnEntryID: 0,
spawnLocationSpawnsID: 0,
respawn: 0,
respawnOffsetLow: 0,
respawnOffsetHigh: 0,
duplicatedSpawn: true,
expireTime: 0,
expireOffset: 0,
xOffset: 0,
yOffset: 0,
zOffset: 0,
deviation: 0,
lootItems: make([]*Item, 0),
lootCoins: 0,
lootGroupID: 0,
lootMethod: MethodFFA,
lootRarity: 0,
looterSpawnID: 0,
lootComplete: make(map[int32]bool),
isLootComplete: false,
isLootDispensed: false,
lootTier: 0,
lootDropType: 0,
trapTriggered: false,
trapState: 0,
chestDropTime: 0,
trapOpenedTime: 0,
merchantID: 0,
merchantType: 0,
merchantMinLevel: 0,
merchantMaxLevel: 0,
isCollector: false,
transporterID: 0,
isTransportSpawn: false,
railID: 0,
railPassengers: make(map[int32]bool),
questIDs: make([]int32, 0),
hasQuestsRequired: false,
hasHistoryRequired: false,
reqQuestsPrivate: false,
reqQuestsOverride: 0,
reqQuestsContinuedAccess: false,
requiredQuests: make(map[int32][]int16),
allowedAccess: make(map[int32]int8),
tmpVisualState: -1,
tmpActionState: -1,
illusionModel: 0,
pickupItemID: 0,
pickupUniqueItemID: 0,
houseCharacterID: 0,
hasSpawnProximities: false,
spawnProximities: make([]*SpawnProximity, 0),
isFlyingCreature: false,
isWaterCreature: false,
isPet: false,
scaredByStrongPlayers: false,
followTarget: 0,
followDistance: 0,
tempVariableTypes: make(map[string]int8),
tempVariables: make(map[string]string),
tempVariableSpawn: make(map[string]int32),
spawnAnim: 0,
addedToWorldTimestamp: 0,
spawnAnimLeeway: 0,
lastHeadingAngle: 0.0,
lastMovementUpdate: int32(time.Now().Unix()),
lastLocationUpdate: 0,
lastGridUpdate: 0,
forceMapCheck: false,
movementLocations: make([]*MovementLocation, 0),
movementLoop: make([]*MovementData, 0),
movementIndex: 0,
runningTo: 0,
invulnerable: false,
spawnGroupList: make([]*Spawn, 0),
establishedGridID: make(map[int32]*TimedGridData),
ignoredWidgets: make(map[int32]bool),
triggerWidgetID: 0,
isOmittedByDBFlag: false,
disableSounds: false,
}
// Initialize appearance with defaults
s.appearance.Pos.State = 0x4080
s.appearance.Difficulty = 6
s.appearance.Pos.CollisionRadius = 32
s.appearance.Pos.Speed1 = 0
// Set alive state
s.isAlive.Store(true)
// Initialize encounter state
s.SetLockedNoLoot(EncounterStateAvailable)
return s
}
// GetID returns the spawn's unique identifier
func (s *Spawn) GetID() int32 {
return s.id
}
// SetID updates the spawn's unique identifier
func (s *Spawn) SetID(id int32) {
s.id = id
}
// GetDatabaseID returns the database ID for this spawn
func (s *Spawn) GetDatabaseID() int32 {
return s.databaseID
}
// SetDatabaseID updates the database ID for this spawn
func (s *Spawn) SetDatabaseID(id int32) {
s.databaseID = id
}
// GetName returns the spawn's display name
func (s *Spawn) GetName() string {
return string(s.appearance.Name[:])
}
// SetName updates the spawn's display name and marks info as changed
func (s *Spawn) SetName(name string) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
copy(s.appearance.Name[:], name)
s.infoChanged.Store(true)
s.changed.Store(true)
s.addChangedZoneSpawn()
}
// GetLevel returns the spawn's level
func (s *Spawn) GetLevel() int16 {
return s.appearance.Level
}
// SetLevel updates the spawn's level and marks info as changed
func (s *Spawn) SetLevel(level int16) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.appearance.Level = level
s.infoChanged.Store(true)
s.changed.Store(true)
s.addChangedZoneSpawn()
}
// GetX returns the spawn's X coordinate
func (s *Spawn) GetX() float32 {
return s.appearance.Pos.X
}
// SetX updates the spawn's X coordinate and marks position as changed
func (s *Spawn) SetX(x float32) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.appearance.Pos.X = x
s.positionChanged.Store(true)
s.infoChanged.Store(true)
s.visChanged.Store(true)
s.changed.Store(true)
s.addChangedZoneSpawn()
}
// GetY returns the spawn's Y coordinate
func (s *Spawn) GetY() float32 {
return s.appearance.Pos.Y
}
// SetY updates the spawn's Y coordinate with optional Y map fixing
func (s *Spawn) SetY(y float32, disableYMapFix bool) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.appearance.Pos.Y = y
s.positionChanged.Store(true)
s.infoChanged.Store(true)
s.visChanged.Store(true)
s.changed.Store(true)
s.addChangedZoneSpawn()
// TODO: Implement Y map fixing when map system is available
if !disableYMapFix {
// FixZ() would be called here
}
}
// GetZ returns the spawn's Z coordinate
func (s *Spawn) GetZ() float32 {
return s.appearance.Pos.Z
}
// SetZ updates the spawn's Z coordinate and marks position as changed
func (s *Spawn) SetZ(z float32) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.appearance.Pos.Z = z
s.positionChanged.Store(true)
s.infoChanged.Store(true)
s.visChanged.Store(true)
s.changed.Store(true)
s.addChangedZoneSpawn()
}
// GetHeading returns the spawn's heading in degrees
func (s *Spawn) GetHeading() float32 {
if s.appearance.Pos.Dir1 == 0 {
return 0
}
heading := float32(s.appearance.Pos.Dir1) / 64.0
if heading >= 180 {
heading -= 180
} else {
heading += 180
}
return heading
}
// SetHeading updates the spawn's heading using raw direction values
func (s *Spawn) SetHeading(dir1, dir2 int16) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.appearance.Pos.Dir1 = dir1
s.appearance.Pos.Dir2 = dir2
s.positionChanged.Store(true)
s.infoChanged.Store(true)
s.visChanged.Store(true)
s.changed.Store(true)
s.addChangedZoneSpawn()
}
// SetHeadingFromFloat updates the spawn's heading using degrees
func (s *Spawn) SetHeadingFromFloat(heading float32) {
s.lastHeadingAngle = heading
if heading != 180 {
heading = (heading - 180) * 64
}
s.SetHeading(int16(heading), int16(heading))
}
// GetDistance calculates distance to specific coordinates
func (s *Spawn) GetDistance(x, y, z float32, ignoreY bool) float32 {
dx := s.GetX() - x
dy := s.GetY() - y
dz := s.GetZ() - z
if ignoreY {
return float32(math.Sqrt(float64(dx*dx + dz*dz)))
}
return float32(math.Sqrt(float64(dx*dx + dy*dy + dz*dz)))
}
// GetDistanceToSpawn calculates distance to another spawn
func (s *Spawn) GetDistanceToSpawn(target *Spawn, ignoreY, includeRadius bool) float32 {
if target == nil {
return 0
}
distance := s.GetDistance(target.GetX(), target.GetY(), target.GetZ(), ignoreY)
if includeRadius {
distance -= s.CalculateRadius(target)
}
return distance
}
// CalculateRadius calculates the combined collision radius for two spawns
func (s *Spawn) CalculateRadius(target *Spawn) float32 {
if target == nil {
return 0
}
myRadius := float32(s.appearance.Pos.CollisionRadius)
targetRadius := float32(target.appearance.Pos.CollisionRadius)
return myRadius + targetRadius
}
// IsAlive returns whether the spawn is currently alive
func (s *Spawn) IsAlive() bool {
return s.isAlive.Load()
}
// SetAlive updates the spawn's alive state
func (s *Spawn) SetAlive(alive bool) {
s.isAlive.Store(alive)
}
// GetHP returns the spawn's current hit points
func (s *Spawn) GetHP() int32 {
return s.basicInfo.CurHP
}
// SetHP updates the spawn's current hit points
func (s *Spawn) SetHP(hp int32) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.basicInfo.CurHP = hp
s.infoChanged.Store(true)
s.changed.Store(true)
s.addChangedZoneSpawn()
}
// GetTotalHP returns the spawn's maximum hit points
func (s *Spawn) GetTotalHP() int32 {
return s.basicInfo.MaxHP
}
// SetTotalHP updates the spawn's maximum hit points
func (s *Spawn) SetTotalHP(maxHP int32) {
s.basicInfo.MaxHP = maxHP
}
// GetPower returns the spawn's current power points
func (s *Spawn) GetPower() int32 {
return s.basicInfo.CurPower
}
// SetPower updates the spawn's current power points
func (s *Spawn) SetPower(power int32) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.basicInfo.CurPower = power
s.infoChanged.Store(true)
s.changed.Store(true)
s.addChangedZoneSpawn()
}
// GetTotalPower returns the spawn's maximum power points
func (s *Spawn) GetTotalPower() int32 {
return s.basicInfo.MaxPower
}
// SetTotalPower updates the spawn's maximum power points
func (s *Spawn) SetTotalPower(maxPower int32) {
s.basicInfo.MaxPower = maxPower
}
// GetLockedNoLoot returns the spawn's loot lock state
func (s *Spawn) GetLockedNoLoot() int8 {
return s.appearance.LockedNoLoot
}
// SetLockedNoLoot updates the spawn's loot lock state
func (s *Spawn) SetLockedNoLoot(state int8) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.appearance.LockedNoLoot = state
s.visChanged.Store(true)
s.changed.Store(true)
s.addChangedZoneSpawn()
}
// AddPrimaryEntityCommand adds a new primary command to the spawn
func (s *Spawn) AddPrimaryEntityCommand(name string, distance float32, command, errorText string, castTime int16, spellVisual int32, defaultAllowList bool) {
s.commandMutex.Lock()
defer s.commandMutex.Unlock()
entityCommand := &EntityCommand{
Name: name,
Distance: distance,
Command: command,
ErrorText: errorText,
CastTime: castTime,
SpellVisual: spellVisual,
AllowOrDeny: make(map[int32]bool),
DefaultAllowList: defaultAllowList,
}
s.primaryCommandList = append(s.primaryCommandList, entityCommand)
}
// RemovePrimaryCommands clears all primary commands
func (s *Spawn) RemovePrimaryCommands() {
s.commandMutex.Lock()
defer s.commandMutex.Unlock()
s.primaryCommandList = make([]*EntityCommand, 0)
}
// GetPrimaryCommands returns the list of primary commands
func (s *Spawn) GetPrimaryCommands() []*EntityCommand {
s.commandMutex.Lock()
defer s.commandMutex.Unlock()
// Return a copy to prevent race conditions
commands := make([]*EntityCommand, len(s.primaryCommandList))
copy(commands, s.primaryCommandList)
return commands
}
// GetSecondaryCommands returns the list of secondary commands
func (s *Spawn) GetSecondaryCommands() []*EntityCommand {
s.commandMutex.Lock()
defer s.commandMutex.Unlock()
// Return a copy to prevent race conditions
commands := make([]*EntityCommand, len(s.secondaryCommandList))
copy(commands, s.secondaryCommandList)
return commands
}
// GetFactionID returns the spawn's faction ID
func (s *Spawn) GetFactionID() int32 {
return s.factionID
}
// SetFactionID updates the spawn's faction ID
func (s *Spawn) SetFactionID(factionID int32) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.factionID = factionID
s.infoChanged.Store(true)
s.changed.Store(true)
s.addChangedZoneSpawn()
}
// GetTarget returns the spawn's current target ID
func (s *Spawn) GetTarget() int32 {
return s.target
}
// SetTarget updates the spawn's current target
func (s *Spawn) SetTarget(targetID int32) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.target = targetID
s.infoChanged.Store(true)
s.changed.Store(true)
s.addChangedZoneSpawn()
}
// GetSize returns the spawn's size
func (s *Spawn) GetSize() int16 {
return s.size
}
// SetSize updates the spawn's size
func (s *Spawn) SetSize(size int16) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.size = size
s.sizeChanged.Store(true)
s.infoChanged.Store(true)
s.changed.Store(true)
s.addChangedZoneSpawn()
}
// GetSpawnType returns the spawn's type
func (s *Spawn) GetSpawnType() int8 {
return s.spawnType
}
// SetSpawnType updates the spawn's type
func (s *Spawn) SetSpawnType(spawnType int8) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.spawnType = spawnType
s.infoChanged.Store(true)
s.changed.Store(true)
s.addChangedZoneSpawn()
}
// SetSpawnScript updates the spawn's Lua script
func (s *Spawn) SetSpawnScript(script string, dbSet bool) {
s.spawnScript = script
s.spawnScriptSetDB = dbSet
}
// GetSpawnScript returns the spawn's Lua script
func (s *Spawn) GetSpawnScript() string {
return s.spawnScript
}
// AddTempVariable stores a temporary string variable for Lua scripting
func (s *Spawn) AddTempVariable(name, value string) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.tempVariables[name] = value
s.tempVariableTypes[name] = 0 // String type
}
// GetTempVariable retrieves a temporary string variable
func (s *Spawn) GetTempVariable(name string) string {
s.updateMutex.RLock()
defer s.updateMutex.RUnlock()
if value, exists := s.tempVariables[name]; exists {
return value
}
return ""
}
// DeleteTempVariable removes a temporary variable
func (s *Spawn) DeleteTempVariable(name string) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
delete(s.tempVariables, name)
delete(s.tempVariableTypes, name)
delete(s.tempVariableSpawn, name)
}
// IsNPC returns whether this spawn is an NPC (to be overridden by subclasses)
func (s *Spawn) IsNPC() bool {
return false
}
// IsPlayer returns whether this spawn is a player (to be overridden by subclasses)
func (s *Spawn) IsPlayer() bool {
return false
}
// IsObject returns whether this spawn is an object (to be overridden by subclasses)
func (s *Spawn) IsObject() bool {
return false
}
// IsWidget returns whether this spawn is a widget (to be overridden by subclasses)
func (s *Spawn) IsWidget() bool {
return false
}
// IsSign returns whether this spawn is a sign (to be overridden by subclasses)
func (s *Spawn) IsSign() bool {
return false
}
// IsGroundSpawn returns whether this spawn is a ground spawn (to be overridden by subclasses)
func (s *Spawn) IsGroundSpawn() bool {
return false
}
// IsEntity returns whether this spawn is an entity (to be overridden by subclasses)
func (s *Spawn) IsEntity() bool {
return false
}
// IsBot returns whether this spawn is a bot (to be overridden by subclasses)
func (s *Spawn) IsBot() bool {
return false
}
// GetAppearanceData returns the spawn's appearance data
func (s *Spawn) GetAppearanceData() *common.AppearanceData {
s.updateMutex.RLock()
defer s.updateMutex.RUnlock()
return &s.appearance
}
// GetBasicInfo returns the spawn's basic info structure
func (s *Spawn) GetBasicInfo() *BasicInfoStruct {
s.updateMutex.RLock()
defer s.updateMutex.RUnlock()
return &s.basicInfo
}
// SetBasicInfo updates the spawn's basic info structure
func (s *Spawn) SetBasicInfo(info *BasicInfoStruct) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
if info != nil {
s.basicInfo = *info
s.infoChanged.Store(true)
s.changed.Store(true)
s.addChangedZoneSpawn()
}
}
// GetClient returns the client associated with this spawn (overridden by Player)
func (s *Spawn) GetClient() interface{} {
return nil // Base spawns have no client
}
// HasChanged returns whether the spawn has any pending changes
func (s *Spawn) HasChanged() bool {
return s.changed.Load()
}
// HasInfoChanged returns whether the spawn's info has changed
func (s *Spawn) HasInfoChanged() bool {
return s.infoChanged.Load()
}
// HasPositionChanged returns whether the spawn's position has changed
func (s *Spawn) HasPositionChanged() bool {
return s.positionChanged.Load()
}
// HasVisualChanged returns whether the spawn's visual appearance has changed
func (s *Spawn) HasVisualChanged() bool {
return s.visChanged.Load()
}
// ClearChanged resets all change flags
func (s *Spawn) ClearChanged() {
s.changed.Store(false)
s.infoChanged.Store(false)
s.positionChanged.Store(false)
s.visChanged.Store(false)
s.sizeChanged.Store(false)
}
// GetLastAttacker returns the ID of the spawn's last attacker
func (s *Spawn) GetLastAttacker() int32 {
return s.lastAttacker
}
// SetLastAttacker updates the spawn's last attacker
func (s *Spawn) SetLastAttacker(attackerID int32) {
s.updateMutex.Lock()
defer s.updateMutex.Unlock()
s.lastAttacker = attackerID
}
// addChangedZoneSpawn notifies the zone that this spawn has changed
// TODO: Implement when zone system is available
func (s *Spawn) addChangedZoneSpawn() {
// This would notify the zone server of spawn changes
// Implementation depends on zone system architecture
}
// TODO: Additional methods to implement:
// - Serialization methods (serialize, spawn_serialize, etc.)
// - Movement system methods
// - Combat and damage methods
// - Loot system methods
// - Group and faction methods
// - Equipment methods
// - Proximity and region methods
// - Knockback physics methods
// - Many more getter/setter methods
// Item placeholder - to be moved to appropriate package when implemented
// Basic structure to prevent compilation errors in Entity system
type Item struct {
ID int32 // Item ID
UniqueID int32 // Unique instance ID
Name string // Item name
Description string // Item description
Icon int32 // Icon ID
Count int16 // Stack count
Price int32 // Item price
Slot int8 // Equipment slot
Type int8 // Item type
SubType int8 // Item subtype
// TODO: Add complete item structure when item system is implemented
// This is a minimal placeholder to allow Entity system compilation
}