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 (using math.MaxInt32 - 1) if id >= 2147483646 { 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 int16(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 = int8(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() any { 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 }