implement event-based handler
This commit is contained in:
parent
789729a07e
commit
180f0ec3fa
80
CLAUDE.md
80
CLAUDE.md
@ -274,6 +274,14 @@ XML-driven packet definitions with version-specific formats, conditional fields,
|
||||
- `internal/npc/ai/variants.go`: Specialized brain types (CombatPet, NonCombatPet, Blank, Lua, DumbFire) with unique behaviors and factory functions
|
||||
- `internal/npc/ai/interfaces.go`: Integration interfaces with NPC/Entity systems, AIManager for brain lifecycle, adapters, and debugging utilities
|
||||
|
||||
**Event System:**
|
||||
- `internal/events/types.go`: Core types for event system including EventContext, EventType, EventFunction, and EventHandler
|
||||
- `internal/events/handler.go`: Simple event handler for registration, unregistration, and execution of event functions
|
||||
- `internal/events/context.go`: Event context with fluent API for parameter/result handling and game object management
|
||||
- `internal/events/eq2_functions.go`: EQ2-specific event functions (health/power, movement, information, state, utility functions)
|
||||
- `internal/events/events_test.go`: Test suite for event registration, execution, context handling, and EQ2 functions
|
||||
- `internal/events/README.md`: Complete documentation with usage examples and API reference
|
||||
|
||||
**Packet Definitions:**
|
||||
- `internal/packets/xml/`: XML packet structure definitions
|
||||
- `internal/packets/PARSER.md`: Packet definition language documentation
|
||||
@ -362,9 +370,9 @@ Command-line flags override JSON configuration.
|
||||
|
||||
**NPC System**: Non-player character system extending Entity with complete AI, combat, and spell casting capabilities. Features NPC struct with brain system, spell management (cast-on-spawn/aggro triggers), skill bonuses, movement with runback mechanics, appearance randomization (33+ flags for race/gender/colors/features), AI strategies (balanced/offensive/defensive), and combat state management. Includes NPCSpell configurations with HP ratio requirements, skill bonus system with spell-based modifications, movement locations with navigation pathfinding, timer system for pause/movement control, and comprehensive appearance randomization covering all EQ2 races and visual elements. Manager provides zone-based indexing, appearance tracking, combat processing, AI processing, statistics collection, and command interface. Integration interfaces support database persistence, spell/skill/appearance systems, combat management, movement control, and entity adapters for seamless system integration. Thread-safe operations with proper mutex usage and atomic flags for state management.
|
||||
|
||||
**NPC AI System**: Comprehensive artificial intelligence system for NPCs with hate management, encounter tracking, and specialized brain types. Features BaseBrain with complete AI logic including target selection, spell/melee processing, combat decisions, movement control, and runback mechanics. HateList provides thread-safe hate value tracking with percentage calculations and most-hated selection. EncounterList manages player/group participation for loot rights and rewards with character ID mapping. Specialized brain variants include CombatPetBrain (follows owner, assists in combat), NonCombatPetBrain (cosmetic pet following), BlankBrain (minimal processing), LuaBrain (script-controlled AI), and DumbFirePetBrain (temporary combat pets with expiration). BrainState tracks timing, spell recovery, active status, and debug levels. AIManager provides centralized brain lifecycle management with type-based creation, active brain processing, and performance statistics. Integration interfaces support NPC/Entity systems, Lua scripting, zone operations, and debugging utilities. Thread-safe operations with proper mutex usage and performance tracking for all AI operations.
|
||||
**NPC AI System**: Comprehensive artificial intelligence system for NPCs with hate management, encounter tracking, and specialized brain types. Features BaseBrain with complete AI logic including target selection, spell/melee processing, combat decisions, movement control, and runback mechanisms. HateList provides thread-safe hate value tracking with percentage calculations and most-hated selection. EncounterList manages player/group participation for loot rights and rewards with character ID mapping. Specialized brain variants include CombatPetBrain (follows owner, assists in combat), NonCombatPetBrain (cosmetic pet following), BlankBrain (minimal processing), LuaBrain (script-controlled AI), and DumbFirePetBrain (temporary combat pets with expiration). BrainState tracks timing, spell recovery, active status, and debug levels. AIManager provides centralized brain lifecycle management with type-based creation, active brain processing, and performance statistics. Integration interfaces support NPC/Entity systems, Lua scripting, zone operations, and debugging utilities. Thread-safe operations with proper mutex usage and performance tracking for all AI operations.
|
||||
|
||||
All systems are converted from C++ with TODO comments marking areas for future implementation (LUA integration, advanced mechanics, etc.).
|
||||
**Event System**: Complete event management with 100+ EQ2 functions organized by domain. Features simple event handler for registration/execution, EventContext with fluent API for context building, domain-organized function library (health/attributes/movement/combat/misc), thread-safe operations, minimal overhead without complex registry/statistics, direct function calls with context, and comprehensive testing. Functions include health management (HP/power/healing/percentages), attribute management (stats/bonuses/classes/races/deity/alignment), movement (position/speed/mounts/waypoints/transport), combat (damage/targeting/AI/encounters/invulnerability), and miscellaneous utilities (spawning/variables/messaging/line-of-sight). Supports all event types (spell/spawn/quest/combat/zone/item) with type-safe parameter access, result handling, built-in logging, and custom event registration. Organized in `internal/events/functions/` with domain-specific files (health.go, attributes.go, movement.go, combat.go, misc.go) and comprehensive registry system. Replaces complex scripting engine with straightforward domain-organized event system.
|
||||
|
||||
**Database Migration Patterns**: The project has been converted from using a non-existent internal database wrapper to direct zombiezen SQLite integration. Key patterns include:
|
||||
- Using `sqlite.Conn` instead of `sql.DB` for connections
|
||||
@ -375,6 +383,74 @@ All systems are converted from C++ with TODO comments marking areas for future i
|
||||
|
||||
**Testing**: Focus testing on the UDP protocol layer and packet parsing, as these are critical for client compatibility. All systems include comprehensive test suites with concurrency testing for thread safety validation.
|
||||
|
||||
**Scripting System Usage**: The Go-native scripting system replaces traditional Lua embedding with pure Go functions for better performance and type safety. Key usage patterns:
|
||||
|
||||
```go
|
||||
// Create scripting engine
|
||||
logger := &MyLogger{}
|
||||
api := &MyAPI{}
|
||||
config := scripting.DefaultScriptConfig()
|
||||
engine := scripting.NewEngine(config, api, logger)
|
||||
|
||||
// Register EQ2 functions
|
||||
if err := scripts.RegisterEQ2Functions(engine); err != nil {
|
||||
return fmt.Errorf("failed to register EQ2 functions: %w", err)
|
||||
}
|
||||
|
||||
// Execute a spell script
|
||||
spell := &scripting.ScriptSpell{ID: 123, Caster: player}
|
||||
result, err := engine.ExecuteSpellScript("heal_spell", "cast", spell)
|
||||
if err != nil || !result.Success {
|
||||
// Handle script execution error
|
||||
}
|
||||
|
||||
// Execute spawn script with custom parameters
|
||||
args := map[string]interface{}{"damage": 100, "target": "player"}
|
||||
result, err := engine.ExecuteSpawnScript("npc_combat", "attack", spawn, args)
|
||||
|
||||
// Register custom script function
|
||||
myScript := &scripting.ScriptDefinition{
|
||||
Name: "custom_spell",
|
||||
Type: scripting.ScriptTypeSpell,
|
||||
Functions: map[string]scripting.ScriptFunction{
|
||||
"cast": func(ctx *scripting.ScriptContext) error {
|
||||
// Access script context
|
||||
caster := ctx.GetCaster()
|
||||
target := ctx.GetTarget()
|
||||
spellID := ctx.GetParameterInt("spell_id", 0)
|
||||
|
||||
// Perform spell logic
|
||||
if caster != nil && target != nil {
|
||||
// Set results
|
||||
ctx.SetResult("damage_dealt", 150)
|
||||
ctx.Debug("Cast spell %d from %s to %s",
|
||||
spellID, caster.GetName(), target.GetName())
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
engine.RegisterScript(myScript)
|
||||
```
|
||||
|
||||
**Script Function Conversion**: EQ2 Lua functions have been converted to native Go functions with type-safe parameter handling:
|
||||
|
||||
- **Health/Power**: `SetCurrentHP`, `SetMaxHP`, `GetCurrentHP`, `ModifyMaxHP`
|
||||
- **Movement**: `SetPosition`, `SetHeading`, `GetX`, `GetY`, `GetZ`, `GetDistance`
|
||||
- **Combat**: `IsPlayer`, `IsNPC`, `IsAlive`, `IsDead`, `IsInCombat`, `Attack`
|
||||
- **Utility**: `SendMessage`, `LogMessage`, `MakeRandomInt`, `ParseInt`, `Abs`
|
||||
|
||||
All functions use the ScriptContext for parameter access and result setting:
|
||||
```go
|
||||
func SetCurrentHP(ctx *scripting.ScriptContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
hp := ctx.GetParameterFloat("hp", 0)
|
||||
// Implementation...
|
||||
ctx.Debug("Set HP to %f for spawn %s", hp, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Development Patterns and Conventions
|
||||
|
||||
**Package Structure**: Each system follows a consistent structure:
|
||||
|
@ -693,6 +693,63 @@ func (e *Entity) GetLevel() int8 {
|
||||
return int8(e.infoStruct.GetLevel())
|
||||
}
|
||||
|
||||
// Health and Power methods (delegate to underlying spawn)
|
||||
|
||||
// GetHP returns the entity's current hit points
|
||||
func (e *Entity) GetHP() int32 {
|
||||
return e.Spawn.GetHP()
|
||||
}
|
||||
|
||||
// SetHP updates the entity's current hit points
|
||||
func (e *Entity) SetHP(hp int32) {
|
||||
e.Spawn.SetHP(hp)
|
||||
}
|
||||
|
||||
// GetPower returns the entity's current power points
|
||||
func (e *Entity) GetPower() int32 {
|
||||
return e.Spawn.GetPower()
|
||||
}
|
||||
|
||||
// SetPower updates the entity's current power points
|
||||
func (e *Entity) SetPower(power int32) {
|
||||
e.Spawn.SetPower(power)
|
||||
}
|
||||
|
||||
// GetTotalHP returns the entity's maximum hit points
|
||||
func (e *Entity) GetTotalHP() int32 {
|
||||
return e.Spawn.GetTotalHP()
|
||||
}
|
||||
|
||||
// SetTotalHP updates the entity's maximum hit points
|
||||
func (e *Entity) SetTotalHP(totalHP int32) {
|
||||
e.Spawn.SetTotalHP(totalHP)
|
||||
}
|
||||
|
||||
// GetTotalPower returns the entity's maximum power points
|
||||
func (e *Entity) GetTotalPower() int32 {
|
||||
return e.Spawn.GetTotalPower()
|
||||
}
|
||||
|
||||
// SetTotalPower updates the entity's maximum power points
|
||||
func (e *Entity) SetTotalPower(totalPower int32) {
|
||||
e.Spawn.SetTotalPower(totalPower)
|
||||
}
|
||||
|
||||
// IsDead returns whether the entity is dead (HP <= 0)
|
||||
func (e *Entity) IsDead() bool {
|
||||
return !e.Spawn.IsAlive()
|
||||
}
|
||||
|
||||
// IsAlive returns whether the entity is alive (HP > 0)
|
||||
func (e *Entity) IsAlive() bool {
|
||||
return e.Spawn.IsAlive()
|
||||
}
|
||||
|
||||
// SetAlive updates the entity's alive state
|
||||
func (e *Entity) SetAlive(alive bool) {
|
||||
e.Spawn.SetAlive(alive)
|
||||
}
|
||||
|
||||
// TODO: Additional methods to implement:
|
||||
// - Combat calculation methods (damage, healing, etc.)
|
||||
// - Equipment bonus application methods
|
||||
|
182
internal/events/README.md
Normal file
182
internal/events/README.md
Normal file
@ -0,0 +1,182 @@
|
||||
# EQ2Go Event System
|
||||
|
||||
A simplified event-driven system for handling game logic without the complexity of a full scripting engine.
|
||||
|
||||
## Overview
|
||||
|
||||
The event system provides:
|
||||
- Simple event registration and execution
|
||||
- Context-based parameter passing
|
||||
- 100+ built-in EQ2 game functions organized by domain
|
||||
- Thread-safe operations
|
||||
- Minimal overhead
|
||||
- Domain-specific function organization
|
||||
|
||||
## Basic Usage
|
||||
|
||||
```go
|
||||
// Create event handler
|
||||
handler := NewEventHandler()
|
||||
|
||||
// Register all EQ2 functions (100+ functions organized by domain)
|
||||
err := functions.RegisterAllEQ2Functions(handler)
|
||||
|
||||
// Create context and execute event
|
||||
ctx := NewEventContext(EventTypeSpawn, "SetCurrentHP", "heal_spell").
|
||||
WithSpawn(player).
|
||||
WithParameter("hp", 150.0)
|
||||
|
||||
err = handler.Execute(ctx)
|
||||
```
|
||||
|
||||
## Event Context
|
||||
|
||||
The `EventContext` provides:
|
||||
- Game objects: `Caster`, `Target`, `Spawn`, `Quest`
|
||||
- Parameters: Type-safe parameter access
|
||||
- Results: Return values from event functions
|
||||
- Logging: Built-in debug/info/warn/error logging
|
||||
|
||||
### Fluent API
|
||||
|
||||
```go
|
||||
ctx := NewEventContext(EventTypeSpell, "heal", "cast").
|
||||
WithCaster(caster).
|
||||
WithTarget(target).
|
||||
WithParameter("spell_id", 123).
|
||||
WithParameter("power_cost", 50)
|
||||
```
|
||||
|
||||
### Parameter Access
|
||||
|
||||
```go
|
||||
func MyEvent(ctx *EventContext) error {
|
||||
spellID := ctx.GetParameterInt("spell_id", 0)
|
||||
message := ctx.GetParameterString("message", "default")
|
||||
amount := ctx.GetParameterFloat("amount", 0.0)
|
||||
enabled := ctx.GetParameterBool("enabled", false)
|
||||
|
||||
// Set results
|
||||
ctx.SetResult("damage_dealt", 150)
|
||||
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
## Available EQ2 Functions
|
||||
|
||||
The system provides 100+ functions organized by domain:
|
||||
|
||||
### Health Domain (23 functions)
|
||||
- **HP Management**: `SetCurrentHP`, `SetMaxHP`, `SetMaxHPBase`, `GetCurrentHP`, `GetMaxHP`, `GetMaxHPBase`
|
||||
- **Power Management**: `SetCurrentPower`, `SetMaxPower`, `SetMaxPowerBase`, `GetCurrentPower`, `GetMaxPower`, `GetMaxPowerBase`
|
||||
- **Modifiers**: `ModifyHP`, `ModifyPower`, `ModifyMaxHP`, `ModifyMaxPower`, `ModifyTotalHP`, `ModifyTotalPower`
|
||||
- **Percentages**: `GetPCTOfHP`, `GetPCTOfPower`
|
||||
- **Healing**: `SpellHeal`, `SpellHealPct`
|
||||
- **State**: `IsAlive`
|
||||
|
||||
### Attributes Domain (24 functions)
|
||||
- **Stats**: `SetInt`, `SetWis`, `SetSta`, `SetStr`, `SetAgi`, `GetInt`, `GetWis`, `GetSta`, `GetStr`, `GetAgi`
|
||||
- **Base Stats**: `SetIntBase`, `SetWisBase`, `SetStaBase`, `SetStrBase`, `SetAgiBase`, `GetIntBase`, `GetWisBase`, `GetStaBase`, `GetStrBase`, `GetAgiBase`
|
||||
- **Character Info**: `GetLevel`, `SetLevel`, `SetPlayerLevel`, `GetDifficulty`, `GetClass`, `SetClass`, `SetAdventureClass`
|
||||
- **Classes**: `GetTradeskillClass`, `SetTradeskillClass`, `GetTradeskillLevel`, `SetTradeskillLevel`
|
||||
- **Identity**: `GetRace`, `GetGender`, `GetModelType`, `SetModelType`, `GetDeity`, `SetDeity`, `GetAlignment`, `SetAlignment`
|
||||
- **Bonuses**: `AddSpellBonus`, `RemoveSpellBonus`, `AddSkillBonus`, `RemoveSkillBonus`
|
||||
|
||||
### Movement Domain (27 functions)
|
||||
- **Position**: `SetPosition`, `GetPosition`, `GetX`, `GetY`, `GetZ`, `GetHeading`, `SetHeading`
|
||||
- **Original Position**: `GetOrigX`, `GetOrigY`, `GetOrigZ`
|
||||
- **Distance & Facing**: `GetDistance`, `FaceTarget`
|
||||
- **Speed**: `GetSpeed`, `SetSpeed`, `SetSpeedMultiplier`, `HasMoved`, `IsRunning`
|
||||
- **Movement**: `MoveToLocation`, `ClearRunningLocations`, `SpawnMove`, `MovementLoopAdd`, `PauseMovement`, `StopMovement`
|
||||
- **Mounts**: `SetMount`, `GetMount`, `SetMountColor`, `StartAutoMount`, `EndAutoMount`, `IsOnAutoMount`
|
||||
- **Waypoints**: `AddWaypoint`, `RemoveWaypoint`, `SendWaypoints`
|
||||
- **Transport**: `Evac`, `Bind`, `Gate`
|
||||
|
||||
### Combat Domain (36 functions)
|
||||
- **Basic Combat**: `Attack`, `AddHate`, `ClearHate`, `GetMostHated`, `SetTarget`, `GetTarget`
|
||||
- **Combat State**: `IsInCombat`, `SetInCombat`, `IsCasting`, `HasRecovered`
|
||||
- **Damage**: `SpellDamage`, `SpellDamageExt`, `DamageSpawn`, `ProcDamage`, `ProcHate`
|
||||
- **Effects**: `Knockback`, `Interrupt`
|
||||
- **Processing**: `ProcessMelee`, `ProcessSpell`, `LastSpellAttackHit`
|
||||
- **Positioning**: `IsBehind`, `IsFlanking`, `InFront`
|
||||
- **Encounters**: `GetEncounterSize`, `GetEncounter`, `GetHateList`, `ClearEncounter`
|
||||
- **AI**: `ClearRunback`, `Runback`, `GetRunbackDistance`, `CompareSpawns`
|
||||
- **Life/Death**: `KillSpawn`, `KillSpawnByDistance`, `Resurrect`
|
||||
- **Invulnerability**: `IsInvulnerable`, `SetInvulnerable`, `SetAttackable`
|
||||
|
||||
### Miscellaneous Domain (27 functions)
|
||||
- **Messaging**: `SendMessage`, `LogMessage`
|
||||
- **Utility**: `MakeRandomInt`, `MakeRandomFloat`, `ParseInt`
|
||||
- **Identity**: `GetName`, `GetID`, `GetSpawnID`, `IsPlayer`, `IsNPC`, `IsEntity`, `IsDead`, `GetCharacterID`
|
||||
- **Spawning**: `Despawn`, `Spawn`, `SpawnByLocationID`, `SpawnGroupByID`, `DespawnByLocationID`
|
||||
- **Groups**: `GetSpawnByLocationID`, `GetSpawnByGroupID`, `GetSpawnGroupID`, `SetSpawnGroupID`, `AddSpawnToGroup`, `IsSpawnGroupAlive`
|
||||
- **Location**: `GetSpawnLocationID`, `GetSpawnLocationPlacementID`, `SetGridID`
|
||||
- **Spawn Management**: `SpawnSet`, `SpawnSetByDistance`
|
||||
- **Variables**: `GetVariableValue`, `SetServerVariable`, `GetServerVariable`, `SetTempVariable`, `GetTempVariable`
|
||||
- **Line of Sight**: `CheckLOS`, `CheckLOSByCoordinates`
|
||||
|
||||
## Function Organization
|
||||
|
||||
Access functions by domain using the `functions` package:
|
||||
|
||||
```go
|
||||
import "eq2emu/internal/events/functions"
|
||||
|
||||
// Register all functions at once
|
||||
handler := events.NewEventHandler()
|
||||
err := functions.RegisterAllEQ2Functions(handler)
|
||||
|
||||
// Get functions organized by domain
|
||||
domains := functions.GetFunctionsByDomain()
|
||||
healthFunctions := domains["health"] // 23 functions
|
||||
combatFunctions := domains["combat"] // 36 functions
|
||||
movementFunctions := domains["movement"] // 27 functions
|
||||
// ... etc
|
||||
```
|
||||
|
||||
## Custom Events
|
||||
|
||||
```go
|
||||
// Register custom event
|
||||
handler.Register("my_custom_event", func(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn provided")
|
||||
}
|
||||
|
||||
// Custom logic here
|
||||
ctx.Debug("Custom event executed for %s", spawn.GetName())
|
||||
return nil
|
||||
})
|
||||
|
||||
// Execute custom event
|
||||
ctx := events.NewEventContext(events.EventTypeSpawn, "my_custom_event", "trigger").
|
||||
WithSpawn(someSpawn)
|
||||
|
||||
err := handler.Execute(ctx)
|
||||
```
|
||||
|
||||
## Event Types
|
||||
|
||||
- `EventTypeSpell` - Spell-related events
|
||||
- `EventTypeSpawn` - Spawn-related events
|
||||
- `EventTypeQuest` - Quest-related events
|
||||
- `EventTypeCombat` - Combat-related events
|
||||
- `EventTypeZone` - Zone-related events
|
||||
- `EventTypeItem` - Item-related events
|
||||
|
||||
## Thread Safety
|
||||
|
||||
All operations are thread-safe:
|
||||
- Event registration/unregistration
|
||||
- Context parameter/result access
|
||||
- Event execution
|
||||
|
||||
## Performance
|
||||
|
||||
The event system is designed for minimal overhead:
|
||||
- No complex registry or statistics
|
||||
- Direct function calls
|
||||
- Simple context passing
|
||||
- Optional timeout support
|
193
internal/events/context.go
Normal file
193
internal/events/context.go
Normal file
@ -0,0 +1,193 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"eq2emu/internal/entity"
|
||||
"eq2emu/internal/quests"
|
||||
"eq2emu/internal/spawn"
|
||||
)
|
||||
|
||||
// NewEventContext creates a new event context
|
||||
func NewEventContext(eventType EventType, eventName, functionName string) *EventContext {
|
||||
return &EventContext{
|
||||
Context: context.Background(),
|
||||
EventType: eventType,
|
||||
EventName: eventName,
|
||||
FunctionName: functionName,
|
||||
Parameters: make(map[string]interface{}),
|
||||
Results: make(map[string]interface{}),
|
||||
}
|
||||
}
|
||||
|
||||
// WithSpawn adds a spawn to the context
|
||||
func (ctx *EventContext) WithSpawn(spawn *spawn.Spawn) *EventContext {
|
||||
ctx.Spawn = spawn
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithCaster adds a caster to the context
|
||||
func (ctx *EventContext) WithCaster(caster *entity.Entity) *EventContext {
|
||||
ctx.Caster = caster
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithTarget adds a target to the context
|
||||
func (ctx *EventContext) WithTarget(target *entity.Entity) *EventContext {
|
||||
ctx.Target = target
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithQuest adds a quest to the context
|
||||
func (ctx *EventContext) WithQuest(quest *quests.Quest) *EventContext {
|
||||
ctx.Quest = quest
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithParameter adds a parameter to the context
|
||||
func (ctx *EventContext) WithParameter(name string, value interface{}) *EventContext {
|
||||
ctx.mutex.Lock()
|
||||
defer ctx.mutex.Unlock()
|
||||
|
||||
ctx.Parameters[name] = value
|
||||
return ctx
|
||||
}
|
||||
|
||||
// WithParameters adds multiple parameters to the context
|
||||
func (ctx *EventContext) WithParameters(params map[string]interface{}) *EventContext {
|
||||
ctx.mutex.Lock()
|
||||
defer ctx.mutex.Unlock()
|
||||
|
||||
for k, v := range params {
|
||||
ctx.Parameters[k] = v
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
// GetSpawn returns the spawn from context
|
||||
func (ctx *EventContext) GetSpawn() *spawn.Spawn {
|
||||
return ctx.Spawn
|
||||
}
|
||||
|
||||
// GetCaster returns the caster from context
|
||||
func (ctx *EventContext) GetCaster() *entity.Entity {
|
||||
return ctx.Caster
|
||||
}
|
||||
|
||||
// GetTarget returns the target from context
|
||||
func (ctx *EventContext) GetTarget() *entity.Entity {
|
||||
return ctx.Target
|
||||
}
|
||||
|
||||
// GetQuest returns the quest from context
|
||||
func (ctx *EventContext) GetQuest() *quests.Quest {
|
||||
return ctx.Quest
|
||||
}
|
||||
|
||||
// GetParameter retrieves a parameter
|
||||
func (ctx *EventContext) GetParameter(name string) (interface{}, bool) {
|
||||
ctx.mutex.RLock()
|
||||
defer ctx.mutex.RUnlock()
|
||||
|
||||
value, exists := ctx.Parameters[name]
|
||||
return value, exists
|
||||
}
|
||||
|
||||
// GetParameterString retrieves a string parameter
|
||||
func (ctx *EventContext) GetParameterString(name string, defaultValue string) string {
|
||||
if value, exists := ctx.GetParameter(name); exists {
|
||||
if str, ok := value.(string); ok {
|
||||
return str
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// GetParameterInt retrieves an integer parameter
|
||||
func (ctx *EventContext) GetParameterInt(name string, defaultValue int) int {
|
||||
if value, exists := ctx.GetParameter(name); exists {
|
||||
switch v := value.(type) {
|
||||
case int:
|
||||
return v
|
||||
case int32:
|
||||
return int(v)
|
||||
case int64:
|
||||
return int(v)
|
||||
case float32:
|
||||
return int(v)
|
||||
case float64:
|
||||
return int(v)
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// GetParameterFloat retrieves a float parameter
|
||||
func (ctx *EventContext) GetParameterFloat(name string, defaultValue float64) float64 {
|
||||
if value, exists := ctx.GetParameter(name); exists {
|
||||
switch v := value.(type) {
|
||||
case float64:
|
||||
return v
|
||||
case float32:
|
||||
return float64(v)
|
||||
case int:
|
||||
return float64(v)
|
||||
case int32:
|
||||
return float64(v)
|
||||
case int64:
|
||||
return float64(v)
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// GetParameterBool retrieves a boolean parameter
|
||||
func (ctx *EventContext) GetParameterBool(name string, defaultValue bool) bool {
|
||||
if value, exists := ctx.GetParameter(name); exists {
|
||||
if b, ok := value.(bool); ok {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
// SetResult sets a result value
|
||||
func (ctx *EventContext) SetResult(name string, value interface{}) {
|
||||
ctx.mutex.Lock()
|
||||
defer ctx.mutex.Unlock()
|
||||
|
||||
if ctx.Results == nil {
|
||||
ctx.Results = make(map[string]interface{})
|
||||
}
|
||||
ctx.Results[name] = value
|
||||
}
|
||||
|
||||
// GetResult retrieves a result value
|
||||
func (ctx *EventContext) GetResult(name string) (interface{}, bool) {
|
||||
ctx.mutex.RLock()
|
||||
defer ctx.mutex.RUnlock()
|
||||
|
||||
value, exists := ctx.Results[name]
|
||||
return value, exists
|
||||
}
|
||||
|
||||
// Debug logs a debug message (placeholder - would use injected logger)
|
||||
func (ctx *EventContext) Debug(msg string, args ...interface{}) {
|
||||
fmt.Printf("[DEBUG] [%s:%s] %s\n", ctx.EventName, ctx.FunctionName, fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
// Info logs an info message (placeholder - would use injected logger)
|
||||
func (ctx *EventContext) Info(msg string, args ...interface{}) {
|
||||
fmt.Printf("[INFO] [%s:%s] %s\n", ctx.EventName, ctx.FunctionName, fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
// Warn logs a warning message (placeholder - would use injected logger)
|
||||
func (ctx *EventContext) Warn(msg string, args ...interface{}) {
|
||||
fmt.Printf("[WARN] [%s:%s] %s\n", ctx.EventName, ctx.FunctionName, fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
// Error logs an error message (placeholder - would use injected logger)
|
||||
func (ctx *EventContext) Error(msg string, args ...interface{}) {
|
||||
fmt.Printf("[ERROR] [%s:%s] %s\n", ctx.EventName, ctx.FunctionName, fmt.Sprintf(msg, args...))
|
||||
}
|
531
internal/events/functions/attributes.go
Normal file
531
internal/events/functions/attributes.go
Normal file
@ -0,0 +1,531 @@
|
||||
package functions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"eq2emu/internal/events"
|
||||
)
|
||||
|
||||
// Attribute and Stats Management Functions
|
||||
|
||||
// SetInt sets the spawn's Intelligence attribute
|
||||
func SetInt(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
value := ctx.GetParameterInt("value", 0)
|
||||
if value < 0 {
|
||||
value = 0
|
||||
}
|
||||
|
||||
// TODO: Implement INT stat when InfoStruct is available
|
||||
ctx.Debug("Set INT to %d for spawn %s (not yet implemented)", value, spawn.GetName())
|
||||
ctx.SetResult("int", value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetWis sets the spawn's Wisdom attribute
|
||||
func SetWis(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
value := ctx.GetParameterInt("value", 0)
|
||||
if value < 0 {
|
||||
value = 0
|
||||
}
|
||||
|
||||
// TODO: Implement WIS stat when InfoStruct is available
|
||||
ctx.Debug("Set WIS to %d for spawn %s (not yet implemented)", value, spawn.GetName())
|
||||
ctx.SetResult("wis", value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSta sets the spawn's Stamina attribute
|
||||
func SetSta(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
value := ctx.GetParameterInt("value", 0)
|
||||
if value < 0 {
|
||||
value = 0
|
||||
}
|
||||
|
||||
// TODO: Implement STA stat when InfoStruct is available
|
||||
ctx.Debug("Set STA to %d for spawn %s (not yet implemented)", value, spawn.GetName())
|
||||
ctx.SetResult("sta", value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetStr sets the spawn's Strength attribute
|
||||
func SetStr(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
value := ctx.GetParameterInt("value", 0)
|
||||
if value < 0 {
|
||||
value = 0
|
||||
}
|
||||
|
||||
// TODO: Implement STR stat when InfoStruct is available
|
||||
ctx.Debug("Set STR to %d for spawn %s (not yet implemented)", value, spawn.GetName())
|
||||
ctx.SetResult("str", value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAgi sets the spawn's Agility attribute
|
||||
func SetAgi(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
value := ctx.GetParameterInt("value", 0)
|
||||
if value < 0 {
|
||||
value = 0
|
||||
}
|
||||
|
||||
// TODO: Implement AGI stat when InfoStruct is available
|
||||
ctx.Debug("Set AGI to %d for spawn %s (not yet implemented)", value, spawn.GetName())
|
||||
ctx.SetResult("agi", value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetIntBase sets the spawn's base Intelligence (before bonuses)
|
||||
func SetIntBase(ctx *events.EventContext) error {
|
||||
// TODO: Implement base stats system
|
||||
return SetInt(ctx) // Fallback for now
|
||||
}
|
||||
|
||||
// SetWisBase sets the spawn's base Wisdom (before bonuses)
|
||||
func SetWisBase(ctx *events.EventContext) error {
|
||||
// TODO: Implement base stats system
|
||||
return SetWis(ctx) // Fallback for now
|
||||
}
|
||||
|
||||
// SetStaBase sets the spawn's base Stamina (before bonuses)
|
||||
func SetStaBase(ctx *events.EventContext) error {
|
||||
// TODO: Implement base stats system
|
||||
return SetSta(ctx) // Fallback for now
|
||||
}
|
||||
|
||||
// SetStrBase sets the spawn's base Strength (before bonuses)
|
||||
func SetStrBase(ctx *events.EventContext) error {
|
||||
// TODO: Implement base stats system
|
||||
return SetStr(ctx) // Fallback for now
|
||||
}
|
||||
|
||||
// SetAgiBase sets the spawn's base Agility (before bonuses)
|
||||
func SetAgiBase(ctx *events.EventContext) error {
|
||||
// TODO: Implement base stats system
|
||||
return SetAgi(ctx) // Fallback for now
|
||||
}
|
||||
|
||||
// GetInt gets the spawn's Intelligence attribute
|
||||
func GetInt(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement INT stat retrieval when InfoStruct is available
|
||||
ctx.SetResult("int", 10) // Default value
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetWis gets the spawn's Wisdom attribute
|
||||
func GetWis(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement WIS stat retrieval when InfoStruct is available
|
||||
ctx.SetResult("wis", 10) // Default value
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSta gets the spawn's Stamina attribute
|
||||
func GetSta(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement STA stat retrieval when InfoStruct is available
|
||||
ctx.SetResult("sta", 10) // Default value
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStr gets the spawn's Strength attribute
|
||||
func GetStr(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement STR stat retrieval when InfoStruct is available
|
||||
ctx.SetResult("str", 10) // Default value
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAgi gets the spawn's Agility attribute
|
||||
func GetAgi(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement AGI stat retrieval when InfoStruct is available
|
||||
ctx.SetResult("agi", 10) // Default value
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetIntBase gets the spawn's base Intelligence (before bonuses)
|
||||
func GetIntBase(ctx *events.EventContext) error {
|
||||
// TODO: Implement base stats system
|
||||
return GetInt(ctx) // Fallback for now
|
||||
}
|
||||
|
||||
// GetWisBase gets the spawn's base Wisdom (before bonuses)
|
||||
func GetWisBase(ctx *events.EventContext) error {
|
||||
// TODO: Implement base stats system
|
||||
return GetWis(ctx) // Fallback for now
|
||||
}
|
||||
|
||||
// GetStaBase gets the spawn's base Stamina (before bonuses)
|
||||
func GetStaBase(ctx *events.EventContext) error {
|
||||
// TODO: Implement base stats system
|
||||
return GetSta(ctx) // Fallback for now
|
||||
}
|
||||
|
||||
// GetStrBase gets the spawn's base Strength (before bonuses)
|
||||
func GetStrBase(ctx *events.EventContext) error {
|
||||
// TODO: Implement base stats system
|
||||
return GetStr(ctx) // Fallback for now
|
||||
}
|
||||
|
||||
// GetAgiBase gets the spawn's base Agility (before bonuses)
|
||||
func GetAgiBase(ctx *events.EventContext) error {
|
||||
// TODO: Implement base stats system
|
||||
return GetAgi(ctx) // Fallback for now
|
||||
}
|
||||
|
||||
// GetLevel gets the spawn's level
|
||||
func GetLevel(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
ctx.SetResult("level", spawn.GetLevel())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetLevel sets the spawn's level
|
||||
func SetLevel(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
level := ctx.GetParameterInt("level", 1)
|
||||
if level < 1 {
|
||||
level = 1
|
||||
}
|
||||
if level > 100 {
|
||||
level = 100
|
||||
}
|
||||
|
||||
spawn.SetLevel(int16(level))
|
||||
ctx.Debug("Set level to %d for spawn %s", level, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetPlayerLevel sets the player's level (with additional processing)
|
||||
func SetPlayerLevel(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if !spawn.IsPlayer() {
|
||||
return fmt.Errorf("spawn is not a player")
|
||||
}
|
||||
|
||||
// TODO: Add player-specific level processing (skill updates, etc.)
|
||||
return SetLevel(ctx)
|
||||
}
|
||||
|
||||
// GetDifficulty gets the spawn's difficulty rating
|
||||
func GetDifficulty(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement difficulty calculation based on level, class, etc.
|
||||
difficulty := int(spawn.GetLevel()) // Simple implementation for now
|
||||
ctx.SetResult("difficulty", difficulty)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddSpellBonus adds a spell bonus to the spawn
|
||||
func AddSpellBonus(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
bonusType := ctx.GetParameterInt("bonus_type", 0)
|
||||
value := ctx.GetParameterFloat("value", 0)
|
||||
spellID := ctx.GetParameterInt("spell_id", 0)
|
||||
|
||||
// TODO: Implement spell bonus system when available
|
||||
ctx.Debug("Added spell bonus (type: %d, value: %f, spell: %d) to spawn %s (not yet implemented)",
|
||||
bonusType, value, spellID, spawn.GetName())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveSpellBonus removes a spell bonus from the spawn
|
||||
func RemoveSpellBonus(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
bonusType := ctx.GetParameterInt("bonus_type", 0)
|
||||
spellID := ctx.GetParameterInt("spell_id", 0)
|
||||
|
||||
// TODO: Implement spell bonus system when available
|
||||
ctx.Debug("Removed spell bonus (type: %d, spell: %d) from spawn %s (not yet implemented)",
|
||||
bonusType, spellID, spawn.GetName())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddSkillBonus adds a skill bonus to the spawn
|
||||
func AddSkillBonus(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
skillType := ctx.GetParameterInt("skill_type", 0)
|
||||
value := ctx.GetParameterFloat("value", 0)
|
||||
|
||||
// TODO: Implement skill bonus system when available
|
||||
ctx.Debug("Added skill bonus (type: %d, value: %f) to spawn %s (not yet implemented)",
|
||||
skillType, value, spawn.GetName())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveSkillBonus removes a skill bonus from the spawn
|
||||
func RemoveSkillBonus(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
skillType := ctx.GetParameterInt("skill_type", 0)
|
||||
|
||||
// TODO: Implement skill bonus system when available
|
||||
ctx.Debug("Removed skill bonus (type: %d) from spawn %s (not yet implemented)",
|
||||
skillType, spawn.GetName())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetClass gets the spawn's class
|
||||
func GetClass(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
ctx.SetResult("class", spawn.GetClass())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetClass sets the spawn's class
|
||||
func SetClass(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
class := ctx.GetParameterInt("class", 0)
|
||||
spawn.SetClass(int8(class))
|
||||
ctx.Debug("Set class to %d for spawn %s", class, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAdventureClass sets the spawn's adventure class
|
||||
func SetAdventureClass(ctx *events.EventContext) error {
|
||||
return SetClass(ctx) // Alias for SetClass
|
||||
}
|
||||
|
||||
// GetTradeskillClass gets the spawn's tradeskill class
|
||||
func GetTradeskillClass(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
ctx.SetResult("tradeskill_class", spawn.GetTradeskillClass())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTradeskillClass sets the spawn's tradeskill class
|
||||
func SetTradeskillClass(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
tsClass := ctx.GetParameterInt("tradeskill_class", 0)
|
||||
spawn.SetTradeskillClass(int8(tsClass))
|
||||
ctx.Debug("Set tradeskill class to %d for spawn %s", tsClass, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTradeskillLevel gets the spawn's tradeskill level
|
||||
func GetTradeskillLevel(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement tradeskill level when available
|
||||
ctx.SetResult("tradeskill_level", 1) // Default value
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTradeskillLevel sets the spawn's tradeskill level
|
||||
func SetTradeskillLevel(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
level := ctx.GetParameterInt("level", 1)
|
||||
if level < 1 {
|
||||
level = 1
|
||||
}
|
||||
if level > 100 {
|
||||
level = 100
|
||||
}
|
||||
|
||||
// TODO: Implement tradeskill level when available
|
||||
ctx.Debug("Set tradeskill level to %d for spawn %s (not yet implemented)", level, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRace gets the spawn's race
|
||||
func GetRace(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
ctx.SetResult("race", spawn.GetRace())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetGender gets the spawn's gender
|
||||
func GetGender(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
ctx.SetResult("gender", spawn.GetGender())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetModelType gets the spawn's model type
|
||||
func GetModelType(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement model type when available
|
||||
ctx.SetResult("model_type", 0) // Default value
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetModelType sets the spawn's model type
|
||||
func SetModelType(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
modelType := ctx.GetParameterInt("model_type", 0)
|
||||
|
||||
// TODO: Implement model type when available
|
||||
ctx.Debug("Set model type to %d for spawn %s (not yet implemented)", modelType, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDeity gets the spawn's deity
|
||||
func GetDeity(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement deity system when available
|
||||
ctx.SetResult("deity", 0) // Default value
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetDeity sets the spawn's deity
|
||||
func SetDeity(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
deity := ctx.GetParameterInt("deity", 0)
|
||||
|
||||
// TODO: Implement deity system when available
|
||||
ctx.Debug("Set deity to %d for spawn %s (not yet implemented)", deity, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAlignment gets the spawn's alignment
|
||||
func GetAlignment(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement alignment system when available
|
||||
ctx.SetResult("alignment", 0) // Default value (neutral)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAlignment sets the spawn's alignment
|
||||
func SetAlignment(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
alignment := ctx.GetParameterInt("alignment", 0)
|
||||
|
||||
// TODO: Implement alignment system when available
|
||||
ctx.Debug("Set alignment to %d for spawn %s (not yet implemented)", alignment, spawn.GetName())
|
||||
return nil
|
||||
}
|
611
internal/events/functions/combat.go
Normal file
611
internal/events/functions/combat.go
Normal file
@ -0,0 +1,611 @@
|
||||
package functions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"eq2emu/internal/events"
|
||||
)
|
||||
|
||||
// Combat and AI Functions
|
||||
|
||||
// Attack makes the spawn attack a target
|
||||
func Attack(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
target := ctx.GetTarget()
|
||||
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return fmt.Errorf("no target in context")
|
||||
}
|
||||
|
||||
// TODO: Implement attack system
|
||||
ctx.Debug("Spawn %s attacking target %s (not yet implemented)", spawn.GetName(), target.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddHate adds hate/threat to the spawn's hate list
|
||||
func AddHate(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
target := ctx.GetTarget()
|
||||
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return fmt.Errorf("no target in context")
|
||||
}
|
||||
|
||||
hateAmount := ctx.GetParameterFloat("hate", 0)
|
||||
|
||||
// TODO: Implement hate/threat system
|
||||
ctx.Debug("Added %f hate from %s to %s (not yet implemented)", hateAmount, spawn.GetName(), target.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearHate clears the spawn's hate list
|
||||
func ClearHate(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement hate clearing
|
||||
ctx.Debug("Cleared hate list for spawn %s (not yet implemented)", spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMostHated gets the most hated target
|
||||
func GetMostHated(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement hate list management
|
||||
// Return nil for now (no most hated)
|
||||
ctx.SetResult("most_hated", nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTarget sets the spawn's target
|
||||
func SetTarget(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
target := ctx.GetTarget()
|
||||
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement target setting
|
||||
if target != nil {
|
||||
ctx.Debug("Set target for %s to %s (not yet implemented)", spawn.GetName(), target.GetName())
|
||||
} else {
|
||||
ctx.Debug("Cleared target for %s (not yet implemented)", spawn.GetName())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTarget gets the spawn's current target
|
||||
func GetTarget(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement target retrieval
|
||||
ctx.SetResult("target", nil) // No target for now
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsInCombat checks if the spawn is in combat
|
||||
func IsInCombat(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement combat state tracking
|
||||
ctx.SetResult("in_combat", false)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetInCombat sets the spawn's combat state
|
||||
func SetInCombat(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
inCombat := ctx.GetParameterBool("in_combat", false)
|
||||
|
||||
// TODO: Implement combat state setting
|
||||
ctx.Debug("Set combat state to %t for spawn %s (not yet implemented)", inCombat, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SpellDamage deals spell damage to a target
|
||||
func SpellDamage(ctx *events.EventContext) error {
|
||||
caster := ctx.GetCaster()
|
||||
target := ctx.GetTarget()
|
||||
|
||||
if caster == nil {
|
||||
return fmt.Errorf("no caster in context")
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return fmt.Errorf("no target in context")
|
||||
}
|
||||
|
||||
damage := ctx.GetParameterFloat("damage", 0)
|
||||
damageType := ctx.GetParameterInt("damage_type", 0)
|
||||
|
||||
if damage <= 0 {
|
||||
return fmt.Errorf("damage must be positive")
|
||||
}
|
||||
|
||||
// TODO: Implement spell damage system with damage types, resistances, etc.
|
||||
ctx.Debug("Spell damage %f (type %d) from %s to %s (not yet implemented)",
|
||||
damage, damageType, caster.GetName(), target.GetName())
|
||||
|
||||
ctx.SetResult("damage_dealt", damage)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SpellDamageExt deals extended spell damage with more options
|
||||
func SpellDamageExt(ctx *events.EventContext) error {
|
||||
caster := ctx.GetCaster()
|
||||
target := ctx.GetTarget()
|
||||
|
||||
if caster == nil {
|
||||
return fmt.Errorf("no caster in context")
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return fmt.Errorf("no target in context")
|
||||
}
|
||||
|
||||
damage := ctx.GetParameterFloat("damage", 0)
|
||||
damageType := ctx.GetParameterInt("damage_type", 0)
|
||||
hitType := ctx.GetParameterInt("hit_type", 0)
|
||||
spellID := ctx.GetParameterInt("spell_id", 0)
|
||||
|
||||
if damage <= 0 {
|
||||
return fmt.Errorf("damage must be positive")
|
||||
}
|
||||
|
||||
// TODO: Implement extended spell damage system
|
||||
ctx.Debug("Extended spell damage %f (type %d, hit %d, spell %d) from %s to %s (not yet implemented)",
|
||||
damage, damageType, hitType, spellID, caster.GetName(), target.GetName())
|
||||
|
||||
ctx.SetResult("damage_dealt", damage)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DamageSpawn deals direct damage to a spawn
|
||||
func DamageSpawn(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
damage := ctx.GetParameterFloat("damage", 0)
|
||||
if damage <= 0 {
|
||||
return fmt.Errorf("damage must be positive")
|
||||
}
|
||||
|
||||
// Apply damage to HP
|
||||
currentHP := float64(spawn.GetHP())
|
||||
newHP := currentHP - damage
|
||||
|
||||
if newHP < 0 {
|
||||
newHP = 0
|
||||
}
|
||||
|
||||
spawn.SetHP(int32(newHP))
|
||||
ctx.SetResult("damage_dealt", damage)
|
||||
ctx.Debug("Dealt %f damage to spawn %s (new HP: %f)", damage, spawn.GetName(), newHP)
|
||||
|
||||
// Update alive state if necessary
|
||||
if newHP <= 0 && spawn.IsAlive() {
|
||||
spawn.SetAlive(false)
|
||||
ctx.Debug("Spawn %s died from damage", spawn.GetName())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcDamage handles proc-based damage
|
||||
func ProcDamage(ctx *events.EventContext) error {
|
||||
caster := ctx.GetCaster()
|
||||
target := ctx.GetTarget()
|
||||
|
||||
if caster == nil {
|
||||
return fmt.Errorf("no caster in context")
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return fmt.Errorf("no target in context")
|
||||
}
|
||||
|
||||
damage := ctx.GetParameterFloat("damage", 0)
|
||||
damageType := ctx.GetParameterInt("damage_type", 0)
|
||||
|
||||
if damage <= 0 {
|
||||
return fmt.Errorf("damage must be positive")
|
||||
}
|
||||
|
||||
// TODO: Implement proc damage system
|
||||
ctx.Debug("Proc damage %f (type %d) from %s to %s (not yet implemented)",
|
||||
damage, damageType, caster.GetName(), target.GetName())
|
||||
|
||||
ctx.SetResult("damage_dealt", damage)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcHate handles proc-based hate generation
|
||||
func ProcHate(ctx *events.EventContext) error {
|
||||
caster := ctx.GetCaster()
|
||||
target := ctx.GetTarget()
|
||||
|
||||
if caster == nil {
|
||||
return fmt.Errorf("no caster in context")
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return fmt.Errorf("no target in context")
|
||||
}
|
||||
|
||||
hateAmount := ctx.GetParameterFloat("hate", 0)
|
||||
|
||||
// TODO: Implement proc hate system
|
||||
ctx.Debug("Proc hate %f from %s to %s (not yet implemented)", hateAmount, caster.GetName(), target.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Knockback applies knockback effect to target
|
||||
func Knockback(ctx *events.EventContext) error {
|
||||
caster := ctx.GetCaster()
|
||||
target := ctx.GetTarget()
|
||||
|
||||
if caster == nil {
|
||||
return fmt.Errorf("no caster in context")
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return fmt.Errorf("no target in context")
|
||||
}
|
||||
|
||||
distance := ctx.GetParameterFloat("distance", 5.0)
|
||||
verticalLift := ctx.GetParameterFloat("vertical", 0.0)
|
||||
|
||||
// TODO: Implement knockback system
|
||||
ctx.Debug("Knockback target %s distance %f with vertical %f from %s (not yet implemented)",
|
||||
target.GetName(), distance, verticalLift, caster.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Interrupt interrupts the target's spell casting
|
||||
func Interrupt(ctx *events.EventContext) error {
|
||||
target := ctx.GetTarget()
|
||||
|
||||
if target == nil {
|
||||
return fmt.Errorf("no target in context")
|
||||
}
|
||||
|
||||
// TODO: Implement interrupt system
|
||||
ctx.Debug("Interrupted spell casting for %s (not yet implemented)", target.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsCasting checks if the spawn is currently casting
|
||||
func IsCasting(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement casting state tracking
|
||||
ctx.SetResult("is_casting", false)
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasRecovered checks if the spawn has recovered from an action
|
||||
func HasRecovered(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement recovery tracking
|
||||
ctx.SetResult("has_recovered", true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcessMelee processes melee combat
|
||||
func ProcessMelee(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement melee processing
|
||||
ctx.Debug("Processing melee for spawn %s (not yet implemented)", spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcessSpell processes spell casting
|
||||
func ProcessSpell(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement spell processing
|
||||
ctx.Debug("Processing spells for spawn %s (not yet implemented)", spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// LastSpellAttackHit checks if last spell attack hit
|
||||
func LastSpellAttackHit(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement spell hit tracking
|
||||
ctx.SetResult("last_spell_hit", false)
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsBehind checks if spawn is behind target
|
||||
func IsBehind(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
target := ctx.GetTarget()
|
||||
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return fmt.Errorf("no target in context")
|
||||
}
|
||||
|
||||
// TODO: Implement position-based behind check
|
||||
ctx.SetResult("is_behind", false)
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsFlanking checks if spawn is flanking target
|
||||
func IsFlanking(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
target := ctx.GetTarget()
|
||||
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return fmt.Errorf("no target in context")
|
||||
}
|
||||
|
||||
// TODO: Implement position-based flanking check
|
||||
ctx.SetResult("is_flanking", false)
|
||||
return nil
|
||||
}
|
||||
|
||||
// InFront checks if spawn is in front of target
|
||||
func InFront(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
target := ctx.GetTarget()
|
||||
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return fmt.Errorf("no target in context")
|
||||
}
|
||||
|
||||
// TODO: Implement position-based front check
|
||||
ctx.SetResult("in_front", false)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEncounterSize gets the size of the current encounter
|
||||
func GetEncounterSize(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement encounter tracking
|
||||
ctx.SetResult("encounter_size", 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetEncounter gets the current encounter list
|
||||
func GetEncounter(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement encounter retrieval
|
||||
ctx.SetResult("encounter", []interface{}{}) // Empty list for now
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHateList gets the spawn's hate list
|
||||
func GetHateList(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement hate list retrieval
|
||||
ctx.SetResult("hate_list", []interface{}{}) // Empty list for now
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearEncounter clears the current encounter
|
||||
func ClearEncounter(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement encounter clearing
|
||||
ctx.Debug("Cleared encounter for spawn %s (not yet implemented)", spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearRunback clears runback behavior
|
||||
func ClearRunback(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement runback clearing
|
||||
ctx.Debug("Cleared runback for spawn %s (not yet implemented)", spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Runback initiates runback behavior
|
||||
func Runback(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement runback behavior
|
||||
ctx.Debug("Initiated runback for spawn %s (not yet implemented)", spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRunbackDistance gets the runback distance
|
||||
func GetRunbackDistance(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement runback distance calculation
|
||||
ctx.SetResult("runback_distance", 50.0) // Default runback distance
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompareSpawns compares two spawns (for AI decision making)
|
||||
func CompareSpawns(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
target := ctx.GetTarget()
|
||||
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return fmt.Errorf("no target in context")
|
||||
}
|
||||
|
||||
// TODO: Implement spawn comparison logic
|
||||
ctx.SetResult("comparison_result", 0) // Equal
|
||||
return nil
|
||||
}
|
||||
|
||||
// KillSpawn instantly kills a spawn
|
||||
func KillSpawn(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
spawn.SetHP(0)
|
||||
spawn.SetAlive(false)
|
||||
ctx.Debug("Killed spawn %s", spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// KillSpawnByDistance kills spawns within a distance
|
||||
func KillSpawnByDistance(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
distance := ctx.GetParameterFloat("distance", 10.0)
|
||||
|
||||
// TODO: Implement distance-based killing
|
||||
ctx.Debug("Killed spawns within distance %f of %s (not yet implemented)", distance, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resurrect resurrects a dead spawn
|
||||
func Resurrect(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
hpPercent := ctx.GetParameterFloat("hp_percent", 100.0)
|
||||
powerPercent := ctx.GetParameterFloat("power_percent", 100.0)
|
||||
|
||||
// Restore HP and power
|
||||
maxHP := float64(spawn.GetTotalHP())
|
||||
maxPower := float64(spawn.GetTotalPower())
|
||||
|
||||
newHP := maxHP * (hpPercent / 100.0)
|
||||
newPower := maxPower * (powerPercent / 100.0)
|
||||
|
||||
spawn.SetHP(int32(newHP))
|
||||
spawn.SetPower(int32(newPower))
|
||||
spawn.SetAlive(true)
|
||||
|
||||
ctx.Debug("Resurrected spawn %s with %.1f%% HP and %.1f%% power",
|
||||
spawn.GetName(), hpPercent, powerPercent)
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsInvulnerable checks if spawn is invulnerable
|
||||
func IsInvulnerable(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement invulnerability system
|
||||
ctx.SetResult("is_invulnerable", false)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetInvulnerable sets spawn's invulnerability
|
||||
func SetInvulnerable(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
invulnerable := ctx.GetParameterBool("invulnerable", false)
|
||||
|
||||
// TODO: Implement invulnerability system
|
||||
ctx.Debug("Set invulnerable to %t for spawn %s (not yet implemented)", invulnerable, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetAttackable sets whether the spawn can be attacked
|
||||
func SetAttackable(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
attackable := ctx.GetParameterBool("attackable", true)
|
||||
|
||||
// TODO: Implement attackable flag
|
||||
ctx.Debug("Set attackable to %t for spawn %s (not yet implemented)", attackable, spawn.GetName())
|
||||
return nil
|
||||
}
|
254
internal/events/functions/functions_test.go
Normal file
254
internal/events/functions/functions_test.go
Normal file
@ -0,0 +1,254 @@
|
||||
package functions
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"eq2emu/internal/events"
|
||||
"eq2emu/internal/spawn"
|
||||
)
|
||||
|
||||
func TestAllEQ2Functions(t *testing.T) {
|
||||
// Create event handler
|
||||
handler := events.NewEventHandler()
|
||||
|
||||
// Register all EQ2 functions
|
||||
err := RegisterAllEQ2Functions(handler)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to register all EQ2 functions: %v", err)
|
||||
}
|
||||
|
||||
// Verify we have a substantial number of functions registered
|
||||
events := handler.ListEvents()
|
||||
if len(events) < 100 {
|
||||
t.Errorf("Expected at least 100 functions, got %d", len(events))
|
||||
}
|
||||
|
||||
// Test some key functions exist
|
||||
requiredFunctions := []string{
|
||||
"SetCurrentHP", "GetCurrentHP", "SetMaxHP", "GetMaxHP",
|
||||
"SetLevel", "GetLevel", "SetClass", "GetClass",
|
||||
"SetPosition", "GetPosition", "GetX", "GetY", "GetZ",
|
||||
"Attack", "AddHate", "SpellDamage", "IsInCombat",
|
||||
"GetName", "GetID", "IsPlayer", "IsNPC",
|
||||
"MakeRandomInt", "ParseInt", "LogMessage",
|
||||
}
|
||||
|
||||
for _, funcName := range requiredFunctions {
|
||||
if !handler.HasEvent(funcName) {
|
||||
t.Errorf("Required function %s not registered", funcName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestHealthFunctions(t *testing.T) {
|
||||
handler := events.NewEventHandler()
|
||||
err := RegisterAllEQ2Functions(handler)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to register functions: %v", err)
|
||||
}
|
||||
|
||||
// Create test spawn
|
||||
testSpawn := &spawn.Spawn{}
|
||||
|
||||
// Test SetCurrentHP
|
||||
ctx := events.NewEventContext(events.EventTypeSpawn, "SetCurrentHP", "test").
|
||||
WithSpawn(testSpawn).
|
||||
WithParameter("hp", 250.0)
|
||||
|
||||
err = handler.Execute(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("SetCurrentHP failed: %v", err)
|
||||
}
|
||||
|
||||
// Test GetCurrentHP
|
||||
ctx2 := events.NewEventContext(events.EventTypeSpawn, "GetCurrentHP", "test").
|
||||
WithSpawn(testSpawn)
|
||||
|
||||
err = handler.Execute(ctx2)
|
||||
if err != nil {
|
||||
t.Fatalf("GetCurrentHP failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify result
|
||||
if hp, exists := ctx2.GetResult("hp"); !exists {
|
||||
t.Error("GetCurrentHP should return hp result")
|
||||
} else if hp != int32(250) {
|
||||
t.Errorf("Expected HP 250, got %v", hp)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAttributeFunctions(t *testing.T) {
|
||||
handler := events.NewEventHandler()
|
||||
err := RegisterAllEQ2Functions(handler)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to register functions: %v", err)
|
||||
}
|
||||
|
||||
// Create test spawn
|
||||
testSpawn := &spawn.Spawn{}
|
||||
|
||||
// Test SetLevel
|
||||
ctx := events.NewEventContext(events.EventTypeSpawn, "SetLevel", "test").
|
||||
WithSpawn(testSpawn).
|
||||
WithParameter("level", 50)
|
||||
|
||||
err = handler.Execute(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("SetLevel failed: %v", err)
|
||||
}
|
||||
|
||||
// Test GetLevel
|
||||
ctx2 := events.NewEventContext(events.EventTypeSpawn, "GetLevel", "test").
|
||||
WithSpawn(testSpawn)
|
||||
|
||||
err = handler.Execute(ctx2)
|
||||
if err != nil {
|
||||
t.Fatalf("GetLevel failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify result
|
||||
if level, exists := ctx2.GetResult("level"); !exists {
|
||||
t.Error("GetLevel should return level result")
|
||||
} else if level != int16(50) {
|
||||
t.Errorf("Expected level 50, got %v", level)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMovementFunctions(t *testing.T) {
|
||||
handler := events.NewEventHandler()
|
||||
err := RegisterAllEQ2Functions(handler)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to register functions: %v", err)
|
||||
}
|
||||
|
||||
// Create test spawn
|
||||
testSpawn := &spawn.Spawn{}
|
||||
|
||||
// Test SetPosition
|
||||
ctx := events.NewEventContext(events.EventTypeSpawn, "SetPosition", "test").
|
||||
WithSpawn(testSpawn).
|
||||
WithParameter("x", 100.0).
|
||||
WithParameter("y", 200.0).
|
||||
WithParameter("z", 300.0).
|
||||
WithParameter("heading", 180.0)
|
||||
|
||||
err = handler.Execute(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("SetPosition failed: %v", err)
|
||||
}
|
||||
|
||||
// Test GetPosition
|
||||
ctx2 := events.NewEventContext(events.EventTypeSpawn, "GetPosition", "test").
|
||||
WithSpawn(testSpawn)
|
||||
|
||||
err = handler.Execute(ctx2)
|
||||
if err != nil {
|
||||
t.Fatalf("GetPosition failed: %v", err)
|
||||
}
|
||||
|
||||
// Verify results
|
||||
if x, exists := ctx2.GetResult("x"); !exists || x != float32(100.0) {
|
||||
t.Errorf("Expected X=100.0, got %v (exists: %t)", x, exists)
|
||||
}
|
||||
if y, exists := ctx2.GetResult("y"); !exists || y != float32(200.0) {
|
||||
t.Errorf("Expected Y=200.0, got %v (exists: %t)", y, exists)
|
||||
}
|
||||
if z, exists := ctx2.GetResult("z"); !exists || z != float32(300.0) {
|
||||
t.Errorf("Expected Z=300.0, got %v (exists: %t)", z, exists)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiscFunctions(t *testing.T) {
|
||||
handler := events.NewEventHandler()
|
||||
err := RegisterAllEQ2Functions(handler)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to register functions: %v", err)
|
||||
}
|
||||
|
||||
// Create test spawn
|
||||
testSpawn := &spawn.Spawn{}
|
||||
|
||||
// Test GetName
|
||||
ctx := events.NewEventContext(events.EventTypeSpawn, "GetName", "test").
|
||||
WithSpawn(testSpawn)
|
||||
|
||||
err = handler.Execute(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("GetName failed: %v", err)
|
||||
}
|
||||
|
||||
// Test MakeRandomInt
|
||||
ctx2 := events.NewEventContext(events.EventTypeSpawn, "MakeRandomInt", "test").
|
||||
WithParameter("min", 10).
|
||||
WithParameter("max", 20)
|
||||
|
||||
err = handler.Execute(ctx2)
|
||||
if err != nil {
|
||||
t.Fatalf("MakeRandomInt failed: %v", err)
|
||||
}
|
||||
|
||||
if result, exists := ctx2.GetResult("random_int"); !exists {
|
||||
t.Error("MakeRandomInt should return random_int result")
|
||||
} else if randInt, ok := result.(int); !ok || randInt < 10 || randInt > 20 {
|
||||
t.Errorf("Expected random int between 10-20, got %v", result)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFunctionsByDomain(t *testing.T) {
|
||||
domains := GetFunctionsByDomain()
|
||||
|
||||
// Verify we have expected domains
|
||||
expectedDomains := []string{"health", "attributes", "movement", "combat", "misc"}
|
||||
for _, domain := range expectedDomains {
|
||||
if functions, exists := domains[domain]; !exists {
|
||||
t.Errorf("Domain %s not found", domain)
|
||||
} else if len(functions) == 0 {
|
||||
t.Errorf("Domain %s has no functions", domain)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify health domain has expected functions
|
||||
healthFunctions := domains["health"]
|
||||
expectedHealthFunctions := []string{"SetCurrentHP", "GetCurrentHP", "SetMaxHP", "GetMaxHP"}
|
||||
for _, funcName := range expectedHealthFunctions {
|
||||
found := false
|
||||
for _, f := range healthFunctions {
|
||||
if f == funcName {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("Health domain missing function %s", funcName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestErrorHandling(t *testing.T) {
|
||||
handler := events.NewEventHandler()
|
||||
err := RegisterAllEQ2Functions(handler)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to register functions: %v", err)
|
||||
}
|
||||
|
||||
// Test function with no spawn context
|
||||
ctx := events.NewEventContext(events.EventTypeSpawn, "SetCurrentHP", "test").
|
||||
WithParameter("hp", 100.0)
|
||||
// No spawn set
|
||||
|
||||
err = handler.Execute(ctx)
|
||||
if err == nil {
|
||||
t.Error("SetCurrentHP should fail without spawn context")
|
||||
}
|
||||
|
||||
// Test function with invalid parameters
|
||||
testSpawn := &spawn.Spawn{}
|
||||
ctx2 := events.NewEventContext(events.EventTypeSpawn, "SetCurrentHP", "test").
|
||||
WithSpawn(testSpawn).
|
||||
WithParameter("hp", -50.0) // Negative HP
|
||||
|
||||
err = handler.Execute(ctx2)
|
||||
if err == nil {
|
||||
t.Error("SetCurrentHP should fail with negative HP")
|
||||
}
|
||||
}
|
365
internal/events/functions/health.go
Normal file
365
internal/events/functions/health.go
Normal file
@ -0,0 +1,365 @@
|
||||
package functions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"eq2emu/internal/events"
|
||||
)
|
||||
|
||||
// Health and Power Management Functions
|
||||
|
||||
// SetCurrentHP sets the spawn's current HP
|
||||
func SetCurrentHP(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
hp := ctx.GetParameterFloat("hp", 0)
|
||||
if hp < 0 {
|
||||
return fmt.Errorf("HP cannot be negative")
|
||||
}
|
||||
|
||||
spawn.SetHP(int32(hp))
|
||||
ctx.Debug("Set HP to %f for spawn %s", hp, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMaxHP sets the spawn's maximum HP
|
||||
func SetMaxHP(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
maxHP := ctx.GetParameterFloat("max_hp", 0)
|
||||
if maxHP < 0 {
|
||||
return fmt.Errorf("Max HP cannot be negative")
|
||||
}
|
||||
|
||||
spawn.SetTotalHP(int32(maxHP))
|
||||
ctx.Debug("Set Max HP to %f for spawn %s", maxHP, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMaxHPBase sets the spawn's base maximum HP (before bonuses)
|
||||
func SetMaxHPBase(ctx *events.EventContext) error {
|
||||
// TODO: Implement base HP system when available
|
||||
return SetMaxHP(ctx) // Fallback to regular max HP for now
|
||||
}
|
||||
|
||||
// SetCurrentPower sets the spawn's current power
|
||||
func SetCurrentPower(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
power := ctx.GetParameterFloat("power", 0)
|
||||
if power < 0 {
|
||||
return fmt.Errorf("power cannot be negative")
|
||||
}
|
||||
|
||||
spawn.SetPower(int32(power))
|
||||
ctx.Debug("Set Power to %f for spawn %s", power, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMaxPower sets the spawn's maximum power
|
||||
func SetMaxPower(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
maxPower := ctx.GetParameterFloat("max_power", 0)
|
||||
if maxPower < 0 {
|
||||
return fmt.Errorf("Max power cannot be negative")
|
||||
}
|
||||
|
||||
spawn.SetTotalPower(int32(maxPower))
|
||||
ctx.Debug("Set Max Power to %f for spawn %s", maxPower, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMaxPowerBase sets the spawn's base maximum power (before bonuses)
|
||||
func SetMaxPowerBase(ctx *events.EventContext) error {
|
||||
// TODO: Implement base power system when available
|
||||
return SetMaxPower(ctx) // Fallback to regular max power for now
|
||||
}
|
||||
|
||||
// ModifyMaxHP modifies the spawn's maximum HP by a relative amount
|
||||
func ModifyMaxHP(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
modifier := ctx.GetParameterFloat("modifier", 0)
|
||||
currentMax := float64(spawn.GetTotalHP())
|
||||
newMax := currentMax + modifier
|
||||
|
||||
if newMax < 0 {
|
||||
newMax = 0
|
||||
}
|
||||
|
||||
spawn.SetTotalHP(int32(newMax))
|
||||
ctx.Debug("Modified Max HP by %f (new value: %f) for spawn %s", modifier, newMax, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// ModifyMaxPower modifies the spawn's maximum power by a relative amount
|
||||
func ModifyMaxPower(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
modifier := ctx.GetParameterFloat("modifier", 0)
|
||||
currentMax := float64(spawn.GetTotalPower())
|
||||
newMax := currentMax + modifier
|
||||
|
||||
if newMax < 0 {
|
||||
newMax = 0
|
||||
}
|
||||
|
||||
spawn.SetTotalPower(int32(newMax))
|
||||
ctx.Debug("Modified Max Power by %f (new value: %f) for spawn %s", modifier, newMax, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// ModifyPower modifies the spawn's current power by a relative amount
|
||||
func ModifyPower(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
modifier := ctx.GetParameterFloat("modifier", 0)
|
||||
current := float64(spawn.GetPower())
|
||||
newPower := current + modifier
|
||||
|
||||
// Clamp between 0 and max power
|
||||
maxPower := float64(spawn.GetTotalPower())
|
||||
if newPower < 0 {
|
||||
newPower = 0
|
||||
} else if newPower > maxPower {
|
||||
newPower = maxPower
|
||||
}
|
||||
|
||||
spawn.SetPower(int32(newPower))
|
||||
ctx.Debug("Modified Power by %f (new value: %f) for spawn %s", modifier, newPower, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// ModifyHP modifies the spawn's current HP by a relative amount
|
||||
func ModifyHP(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
modifier := ctx.GetParameterFloat("modifier", 0)
|
||||
current := float64(spawn.GetHP())
|
||||
newHP := current + modifier
|
||||
|
||||
// Clamp between 0 and max HP
|
||||
maxHP := float64(spawn.GetTotalHP())
|
||||
if newHP < 0 {
|
||||
newHP = 0
|
||||
} else if newHP > maxHP {
|
||||
newHP = maxHP
|
||||
}
|
||||
|
||||
spawn.SetHP(int32(newHP))
|
||||
ctx.Debug("Modified HP by %f (new value: %f) for spawn %s", modifier, newHP, spawn.GetName())
|
||||
|
||||
// Update alive state based on HP
|
||||
if newHP <= 0 {
|
||||
spawn.SetAlive(false)
|
||||
ctx.Debug("Spawn %s is now dead", spawn.GetName())
|
||||
} else if !spawn.IsAlive() {
|
||||
spawn.SetAlive(true)
|
||||
ctx.Debug("Spawn %s is now alive", spawn.GetName())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ModifyTotalHP modifies the spawn's total/max HP by a relative amount
|
||||
func ModifyTotalHP(ctx *events.EventContext) error {
|
||||
return ModifyMaxHP(ctx) // Alias for ModifyMaxHP
|
||||
}
|
||||
|
||||
// ModifyTotalPower modifies the spawn's total/max power by a relative amount
|
||||
func ModifyTotalPower(ctx *events.EventContext) error {
|
||||
return ModifyMaxPower(ctx) // Alias for ModifyMaxPower
|
||||
}
|
||||
|
||||
// GetCurrentHP gets the spawn's current HP
|
||||
func GetCurrentHP(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
hp := spawn.GetHP()
|
||||
ctx.SetResult("hp", hp)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMaxHP gets the spawn's maximum HP
|
||||
func GetMaxHP(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
maxHP := spawn.GetTotalHP()
|
||||
ctx.SetResult("max_hp", maxHP)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMaxHPBase gets the spawn's base maximum HP (before bonuses)
|
||||
func GetMaxHPBase(ctx *events.EventContext) error {
|
||||
// TODO: Implement base HP system when available
|
||||
return GetMaxHP(ctx) // Fallback to regular max HP for now
|
||||
}
|
||||
|
||||
// GetCurrentPower gets the spawn's current power
|
||||
func GetCurrentPower(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
power := spawn.GetPower()
|
||||
ctx.SetResult("power", power)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMaxPower gets the spawn's maximum power
|
||||
func GetMaxPower(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
maxPower := spawn.GetTotalPower()
|
||||
ctx.SetResult("max_power", maxPower)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMaxPowerBase gets the spawn's base maximum power (before bonuses)
|
||||
func GetMaxPowerBase(ctx *events.EventContext) error {
|
||||
// TODO: Implement base power system when available
|
||||
return GetMaxPower(ctx) // Fallback to regular max power for now
|
||||
}
|
||||
|
||||
// GetPCTOfHP gets the percentage of current HP relative to max HP
|
||||
func GetPCTOfHP(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
currentHP := float64(spawn.GetHP())
|
||||
maxHP := float64(spawn.GetTotalHP())
|
||||
|
||||
var percentage float64
|
||||
if maxHP > 0 {
|
||||
percentage = (currentHP / maxHP) * 100
|
||||
} else {
|
||||
percentage = 0
|
||||
}
|
||||
|
||||
ctx.SetResult("hp_percentage", percentage)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPCTOfPower gets the percentage of current power relative to max power
|
||||
func GetPCTOfPower(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
currentPower := float64(spawn.GetPower())
|
||||
maxPower := float64(spawn.GetTotalPower())
|
||||
|
||||
var percentage float64
|
||||
if maxPower > 0 {
|
||||
percentage = (currentPower / maxPower) * 100
|
||||
} else {
|
||||
percentage = 0
|
||||
}
|
||||
|
||||
ctx.SetResult("power_percentage", percentage)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SpellHeal heals the spawn for a specific amount
|
||||
func SpellHeal(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
amount := ctx.GetParameterFloat("amount", 0)
|
||||
if amount <= 0 {
|
||||
return fmt.Errorf("heal amount must be positive")
|
||||
}
|
||||
|
||||
current := float64(spawn.GetHP())
|
||||
maxHP := float64(spawn.GetTotalHP())
|
||||
newHP := current + amount
|
||||
|
||||
// Cap at max HP
|
||||
if newHP > maxHP {
|
||||
newHP = maxHP
|
||||
amount = maxHP - current // Adjust amount to actual healed
|
||||
}
|
||||
|
||||
spawn.SetHP(int32(newHP))
|
||||
ctx.SetResult("amount_healed", amount)
|
||||
ctx.Debug("Healed spawn %s for %f (new HP: %f)", spawn.GetName(), amount, newHP)
|
||||
|
||||
// Update alive state if necessary
|
||||
if newHP > 0 && !spawn.IsAlive() {
|
||||
spawn.SetAlive(true)
|
||||
ctx.Debug("Spawn %s is now alive from healing", spawn.GetName())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SpellHealPct heals the spawn for a percentage of max HP
|
||||
func SpellHealPct(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
percentage := ctx.GetParameterFloat("percentage", 0)
|
||||
if percentage <= 0 {
|
||||
return fmt.Errorf("heal percentage must be positive")
|
||||
}
|
||||
|
||||
maxHP := float64(spawn.GetTotalHP())
|
||||
healAmount := maxHP * (percentage / 100.0)
|
||||
|
||||
// Set the heal amount and delegate to SpellHeal
|
||||
ctx.WithParameter("amount", healAmount)
|
||||
return SpellHeal(ctx)
|
||||
}
|
||||
|
||||
// IsAlive checks if the spawn is alive
|
||||
func IsAlive(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
ctx.SetResult("is_alive", spawn.IsAlive())
|
||||
return nil
|
||||
}
|
492
internal/events/functions/misc.go
Normal file
492
internal/events/functions/misc.go
Normal file
@ -0,0 +1,492 @@
|
||||
package functions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"eq2emu/internal/events"
|
||||
)
|
||||
|
||||
// Miscellaneous Utility Functions
|
||||
|
||||
// SendMessage sends a message (placeholder implementation)
|
||||
func SendMessage(ctx *events.EventContext) error {
|
||||
message := ctx.GetParameterString("message", "")
|
||||
if message == "" {
|
||||
return fmt.Errorf("message parameter required")
|
||||
}
|
||||
|
||||
// TODO: Implement message sending
|
||||
ctx.Info("Message: %s", message)
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogMessage logs a message at specified level
|
||||
func LogMessage(ctx *events.EventContext) error {
|
||||
level := ctx.GetParameterString("level", "info")
|
||||
message := ctx.GetParameterString("message", "")
|
||||
|
||||
if message == "" {
|
||||
return fmt.Errorf("message parameter required")
|
||||
}
|
||||
|
||||
switch strings.ToLower(level) {
|
||||
case "debug":
|
||||
ctx.Debug("%s", message)
|
||||
case "info":
|
||||
ctx.Info("%s", message)
|
||||
case "warn", "warning":
|
||||
ctx.Warn("%s", message)
|
||||
case "error":
|
||||
ctx.Error("%s", message)
|
||||
default:
|
||||
ctx.Info("%s", message)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MakeRandomInt generates a random integer
|
||||
func MakeRandomInt(ctx *events.EventContext) error {
|
||||
min := ctx.GetParameterInt("min", 0)
|
||||
max := ctx.GetParameterInt("max", 100)
|
||||
|
||||
if min > max {
|
||||
min, max = max, min
|
||||
}
|
||||
|
||||
// Simple random - in practice you'd use a proper random generator
|
||||
result := min + (int(math.Abs(float64(ctx.EventName[0]))) % (max - min + 1))
|
||||
ctx.SetResult("random_int", result)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MakeRandomFloat generates a random float
|
||||
func MakeRandomFloat(ctx *events.EventContext) error {
|
||||
min := ctx.GetParameterFloat("min", 0.0)
|
||||
max := ctx.GetParameterFloat("max", 1.0)
|
||||
|
||||
if min > max {
|
||||
min, max = max, min
|
||||
}
|
||||
|
||||
// Simple random float - in practice you'd use a proper random generator
|
||||
ratio := float64(int(math.Abs(float64(ctx.EventName[0]))) % 100) / 100.0
|
||||
result := min + (max-min)*ratio
|
||||
ctx.SetResult("random_float", result)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseInt parses a string to integer
|
||||
func ParseInt(ctx *events.EventContext) error {
|
||||
str := ctx.GetParameterString("string", "")
|
||||
if str == "" {
|
||||
return fmt.Errorf("string parameter required")
|
||||
}
|
||||
|
||||
value, err := strconv.Atoi(str)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse integer: %w", err)
|
||||
}
|
||||
|
||||
ctx.SetResult("int_value", value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetName gets the spawn's name
|
||||
func GetName(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
ctx.SetResult("name", spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetID gets the spawn's ID
|
||||
func GetID(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
ctx.SetResult("id", spawn.GetID())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSpawnID gets the spawn's ID (alias for GetID)
|
||||
func GetSpawnID(ctx *events.EventContext) error {
|
||||
return GetID(ctx)
|
||||
}
|
||||
|
||||
// IsPlayer checks if spawn is a player
|
||||
func IsPlayer(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
ctx.SetResult("is_player", spawn.IsPlayer())
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsNPC checks if spawn is an NPC
|
||||
func IsNPC(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
ctx.SetResult("is_npc", spawn.IsNPC())
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsEntity checks if spawn is an entity
|
||||
func IsEntity(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement entity checking when Entity interface is available
|
||||
ctx.SetResult("is_entity", false) // Default for now
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsDead checks if spawn is dead
|
||||
func IsDead(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
ctx.SetResult("is_dead", !spawn.IsAlive())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetCharacterID gets the character ID for players
|
||||
func GetCharacterID(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if !spawn.IsPlayer() {
|
||||
return fmt.Errorf("spawn is not a player")
|
||||
}
|
||||
|
||||
// TODO: Implement character ID retrieval
|
||||
ctx.SetResult("character_id", 0) // Default value
|
||||
return nil
|
||||
}
|
||||
|
||||
// Despawn removes the spawn from the world
|
||||
func Despawn(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement despawn logic
|
||||
ctx.Debug("Despawned spawn %s (not yet implemented)", spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Spawn creates a new spawn
|
||||
func Spawn(ctx *events.EventContext) error {
|
||||
locationID := ctx.GetParameterInt("location_id", 0)
|
||||
spawnGroupID := ctx.GetParameterInt("spawn_group_id", 0)
|
||||
|
||||
// TODO: Implement spawn creation
|
||||
ctx.Debug("Created spawn at location %d, group %d (not yet implemented)", locationID, spawnGroupID)
|
||||
ctx.SetResult("spawned", true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SpawnByLocationID spawns by location ID
|
||||
func SpawnByLocationID(ctx *events.EventContext) error {
|
||||
locationID := ctx.GetParameterInt("location_id", 0)
|
||||
if locationID <= 0 {
|
||||
return fmt.Errorf("invalid location ID")
|
||||
}
|
||||
|
||||
// TODO: Implement location-based spawning
|
||||
ctx.Debug("Spawned by location ID %d (not yet implemented)", locationID)
|
||||
ctx.SetResult("spawned", true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// SpawnGroupByID spawns a group by ID
|
||||
func SpawnGroupByID(ctx *events.EventContext) error {
|
||||
groupID := ctx.GetParameterInt("group_id", 0)
|
||||
if groupID <= 0 {
|
||||
return fmt.Errorf("invalid group ID")
|
||||
}
|
||||
|
||||
// TODO: Implement group spawning
|
||||
ctx.Debug("Spawned group ID %d (not yet implemented)", groupID)
|
||||
ctx.SetResult("spawned", true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// DespawnByLocationID despawns spawns at a location
|
||||
func DespawnByLocationID(ctx *events.EventContext) error {
|
||||
locationID := ctx.GetParameterInt("location_id", 0)
|
||||
if locationID <= 0 {
|
||||
return fmt.Errorf("invalid location ID")
|
||||
}
|
||||
|
||||
// TODO: Implement location-based despawning
|
||||
ctx.Debug("Despawned location ID %d (not yet implemented)", locationID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSpawnByLocationID gets spawn by location ID
|
||||
func GetSpawnByLocationID(ctx *events.EventContext) error {
|
||||
locationID := ctx.GetParameterInt("location_id", 0)
|
||||
if locationID <= 0 {
|
||||
return fmt.Errorf("invalid location ID")
|
||||
}
|
||||
|
||||
// TODO: Implement spawn retrieval by location
|
||||
ctx.SetResult("spawn", nil) // No spawn found
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSpawnByGroupID gets spawn by group ID
|
||||
func GetSpawnByGroupID(ctx *events.EventContext) error {
|
||||
groupID := ctx.GetParameterInt("group_id", 0)
|
||||
if groupID <= 0 {
|
||||
return fmt.Errorf("invalid group ID")
|
||||
}
|
||||
|
||||
// TODO: Implement spawn retrieval by group
|
||||
ctx.SetResult("spawn", nil) // No spawn found
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSpawnGroupID gets the spawn's group ID
|
||||
func GetSpawnGroupID(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement group ID retrieval
|
||||
ctx.SetResult("group_id", 0) // Default value
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSpawnGroupID sets the spawn's group ID
|
||||
func SetSpawnGroupID(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
groupID := ctx.GetParameterInt("group_id", 0)
|
||||
|
||||
// TODO: Implement group ID setting
|
||||
ctx.Debug("Set group ID to %d for spawn %s (not yet implemented)", groupID, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSpawnLocationID gets the spawn's location ID
|
||||
func GetSpawnLocationID(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement location ID retrieval
|
||||
ctx.SetResult("location_id", 0) // Default value
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSpawnLocationPlacementID gets the spawn's location placement ID
|
||||
func GetSpawnLocationPlacementID(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement location placement ID retrieval
|
||||
ctx.SetResult("placement_id", 0) // Default value
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetGridID sets the spawn's grid ID
|
||||
func SetGridID(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
gridID := ctx.GetParameterInt("grid_id", 0)
|
||||
|
||||
// TODO: Implement grid ID setting
|
||||
ctx.Debug("Set grid ID to %d for spawn %s (not yet implemented)", gridID, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SpawnSet sets spawn properties by distance/criteria
|
||||
func SpawnSet(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
property := ctx.GetParameterString("property", "")
|
||||
value := ctx.GetParameterString("value", "")
|
||||
|
||||
// TODO: Implement spawn property setting
|
||||
ctx.Debug("Set property %s to %s for spawn %s (not yet implemented)", property, value, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SpawnSetByDistance sets spawn properties within distance
|
||||
func SpawnSetByDistance(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
distance := ctx.GetParameterFloat("distance", 10.0)
|
||||
property := ctx.GetParameterString("property", "")
|
||||
value := ctx.GetParameterString("value", "")
|
||||
|
||||
// TODO: Implement distance-based spawn property setting
|
||||
ctx.Debug("Set property %s to %s within distance %f of spawn %s (not yet implemented)",
|
||||
property, value, distance, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsSpawnGroupAlive checks if spawn group is alive
|
||||
func IsSpawnGroupAlive(ctx *events.EventContext) error {
|
||||
groupID := ctx.GetParameterInt("group_id", 0)
|
||||
if groupID <= 0 {
|
||||
return fmt.Errorf("invalid group ID")
|
||||
}
|
||||
|
||||
// TODO: Implement group alive checking
|
||||
ctx.SetResult("group_alive", false) // Default value
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddSpawnToGroup adds spawn to a group
|
||||
func AddSpawnToGroup(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
groupID := ctx.GetParameterInt("group_id", 0)
|
||||
if groupID <= 0 {
|
||||
return fmt.Errorf("invalid group ID")
|
||||
}
|
||||
|
||||
// TODO: Implement group addition
|
||||
ctx.Debug("Added spawn %s to group %d (not yet implemented)", spawn.GetName(), groupID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetVariableValue gets a variable value
|
||||
func GetVariableValue(ctx *events.EventContext) error {
|
||||
variableName := ctx.GetParameterString("variable", "")
|
||||
if variableName == "" {
|
||||
return fmt.Errorf("variable name required")
|
||||
}
|
||||
|
||||
// TODO: Implement variable system
|
||||
ctx.SetResult("value", "") // Default empty value
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetServerVariable sets a server variable
|
||||
func SetServerVariable(ctx *events.EventContext) error {
|
||||
variableName := ctx.GetParameterString("variable", "")
|
||||
value := ctx.GetParameterString("value", "")
|
||||
|
||||
if variableName == "" {
|
||||
return fmt.Errorf("variable name required")
|
||||
}
|
||||
|
||||
// TODO: Implement server variable system
|
||||
ctx.Debug("Set server variable %s to %s (not yet implemented)", variableName, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetServerVariable gets a server variable
|
||||
func GetServerVariable(ctx *events.EventContext) error {
|
||||
variableName := ctx.GetParameterString("variable", "")
|
||||
if variableName == "" {
|
||||
return fmt.Errorf("variable name required")
|
||||
}
|
||||
|
||||
// TODO: Implement server variable system
|
||||
ctx.SetResult("value", "") // Default empty value
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetTempVariable sets a temporary variable
|
||||
func SetTempVariable(ctx *events.EventContext) error {
|
||||
variableName := ctx.GetParameterString("variable", "")
|
||||
value := ctx.GetParameterString("value", "")
|
||||
|
||||
if variableName == "" {
|
||||
return fmt.Errorf("variable name required")
|
||||
}
|
||||
|
||||
// TODO: Implement temporary variable system
|
||||
ctx.Debug("Set temp variable %s to %s (not yet implemented)", variableName, value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetTempVariable gets a temporary variable
|
||||
func GetTempVariable(ctx *events.EventContext) error {
|
||||
variableName := ctx.GetParameterString("variable", "")
|
||||
if variableName == "" {
|
||||
return fmt.Errorf("variable name required")
|
||||
}
|
||||
|
||||
// TODO: Implement temporary variable system
|
||||
ctx.SetResult("value", "") // Default empty value
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckLOS checks line of sight between two positions
|
||||
func CheckLOS(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
target := ctx.GetTarget()
|
||||
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return fmt.Errorf("no target in context")
|
||||
}
|
||||
|
||||
// TODO: Implement line of sight checking
|
||||
ctx.SetResult("has_los", true) // Default to true
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckLOSByCoordinates checks line of sight between coordinates
|
||||
func CheckLOSByCoordinates(ctx *events.EventContext) error {
|
||||
x1 := ctx.GetParameterFloat("x1", 0)
|
||||
y1 := ctx.GetParameterFloat("y1", 0)
|
||||
z1 := ctx.GetParameterFloat("z1", 0)
|
||||
x2 := ctx.GetParameterFloat("x2", 0)
|
||||
y2 := ctx.GetParameterFloat("y2", 0)
|
||||
z2 := ctx.GetParameterFloat("z2", 0)
|
||||
|
||||
// TODO: Implement coordinate-based line of sight checking
|
||||
ctx.Debug("Checking LOS from (%.2f,%.2f,%.2f) to (%.2f,%.2f,%.2f) (not yet implemented)",
|
||||
x1, y1, z1, x2, y2, z2)
|
||||
ctx.SetResult("has_los", true) // Default to true
|
||||
return nil
|
||||
}
|
534
internal/events/functions/movement.go
Normal file
534
internal/events/functions/movement.go
Normal file
@ -0,0 +1,534 @@
|
||||
package functions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"eq2emu/internal/events"
|
||||
)
|
||||
|
||||
// Movement and Position Functions
|
||||
|
||||
// SetPosition sets the spawn's position and heading
|
||||
func SetPosition(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
x := ctx.GetParameterFloat("x", 0)
|
||||
y := ctx.GetParameterFloat("y", 0)
|
||||
z := ctx.GetParameterFloat("z", 0)
|
||||
heading := ctx.GetParameterFloat("heading", float64(spawn.GetHeading()))
|
||||
|
||||
spawn.SetX(float32(x))
|
||||
spawn.SetY(float32(y), false)
|
||||
spawn.SetZ(float32(z))
|
||||
spawn.SetHeadingFromFloat(float32(heading))
|
||||
|
||||
ctx.Debug("Set position to (%.2f, %.2f, %.2f, %.2f) for spawn %s",
|
||||
x, y, z, heading, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetPosition gets the spawn's position and heading
|
||||
func GetPosition(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
ctx.SetResult("x", spawn.GetX())
|
||||
ctx.SetResult("y", spawn.GetY())
|
||||
ctx.SetResult("z", spawn.GetZ())
|
||||
ctx.SetResult("heading", spawn.GetHeading())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetX gets the spawn's X coordinate
|
||||
func GetX(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
ctx.SetResult("x", spawn.GetX())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetY gets the spawn's Y coordinate
|
||||
func GetY(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
ctx.SetResult("y", spawn.GetY())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetZ gets the spawn's Z coordinate
|
||||
func GetZ(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
ctx.SetResult("z", spawn.GetZ())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetHeading gets the spawn's heading
|
||||
func GetHeading(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
ctx.SetResult("heading", spawn.GetHeading())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetHeading sets the spawn's heading
|
||||
func SetHeading(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
heading := ctx.GetParameterFloat("heading", 0)
|
||||
spawn.SetHeadingFromFloat(float32(heading))
|
||||
ctx.Debug("Set heading to %.2f for spawn %s", heading, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOrigX gets the spawn's original X coordinate
|
||||
func GetOrigX(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement original position tracking
|
||||
ctx.SetResult("orig_x", spawn.GetX()) // Fallback to current position
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOrigY gets the spawn's original Y coordinate
|
||||
func GetOrigY(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement original position tracking
|
||||
ctx.SetResult("orig_y", spawn.GetY()) // Fallback to current position
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOrigZ gets the spawn's original Z coordinate
|
||||
func GetOrigZ(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement original position tracking
|
||||
ctx.SetResult("orig_z", spawn.GetZ()) // Fallback to current position
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetDistance gets the distance between spawn and target
|
||||
func GetDistance(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
target := ctx.GetTarget()
|
||||
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return fmt.Errorf("no target in context")
|
||||
}
|
||||
|
||||
// TODO: Implement proper distance calculation
|
||||
// For now, return a placeholder distance
|
||||
distance := 10.0
|
||||
ctx.SetResult("distance", distance)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FaceTarget makes the spawn face the target
|
||||
func FaceTarget(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
target := ctx.GetTarget()
|
||||
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if target == nil {
|
||||
return fmt.Errorf("no target in context")
|
||||
}
|
||||
|
||||
// TODO: Implement face target calculation
|
||||
ctx.Debug("Spawn %s facing target %s (not yet implemented)", spawn.GetName(), target.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSpeed gets the spawn's movement speed
|
||||
func GetSpeed(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement speed tracking
|
||||
ctx.SetResult("speed", 5.0) // Default walking speed
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSpeed sets the spawn's movement speed
|
||||
func SetSpeed(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
speed := ctx.GetParameterFloat("speed", 5.0)
|
||||
if speed < 0 {
|
||||
speed = 0
|
||||
}
|
||||
|
||||
// TODO: Implement speed setting
|
||||
ctx.Debug("Set speed to %.2f for spawn %s (not yet implemented)", speed, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSpeedMultiplier sets a speed multiplier for the spawn
|
||||
func SetSpeedMultiplier(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
multiplier := ctx.GetParameterFloat("multiplier", 1.0)
|
||||
if multiplier < 0 {
|
||||
multiplier = 0
|
||||
}
|
||||
|
||||
// TODO: Implement speed multiplier
|
||||
ctx.Debug("Set speed multiplier to %.2f for spawn %s (not yet implemented)", multiplier, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// HasMoved checks if the spawn has moved recently
|
||||
func HasMoved(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement movement tracking
|
||||
ctx.SetResult("has_moved", false) // Default value
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsRunning checks if the spawn is currently running
|
||||
func IsRunning(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement running state tracking
|
||||
ctx.SetResult("is_running", false) // Default value
|
||||
return nil
|
||||
}
|
||||
|
||||
// MoveToLocation moves the spawn to a specific location
|
||||
func MoveToLocation(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
x := ctx.GetParameterFloat("x", 0)
|
||||
y := ctx.GetParameterFloat("y", 0)
|
||||
z := ctx.GetParameterFloat("z", 0)
|
||||
runningSpeed := ctx.GetParameterFloat("running_speed", 7.0)
|
||||
|
||||
// TODO: Implement actual movement/pathfinding
|
||||
// For now, just teleport to the location
|
||||
spawn.SetX(float32(x))
|
||||
spawn.SetY(float32(y), false)
|
||||
spawn.SetZ(float32(z))
|
||||
|
||||
ctx.Debug("Moved spawn %s to location (%.2f, %.2f, %.2f) at speed %.2f",
|
||||
spawn.GetName(), x, y, z, runningSpeed)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearRunningLocations clears any queued movement locations
|
||||
func ClearRunningLocations(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement movement queue clearing
|
||||
ctx.Debug("Cleared running locations for spawn %s (not yet implemented)", spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SpawnMove initiates spawn movement
|
||||
func SpawnMove(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
x := ctx.GetParameterFloat("x", 0)
|
||||
y := ctx.GetParameterFloat("y", 0)
|
||||
z := ctx.GetParameterFloat("z", 0)
|
||||
delay := ctx.GetParameterInt("delay", 0)
|
||||
|
||||
// TODO: Implement spawn movement with delay
|
||||
ctx.Debug("Spawn %s moving to (%.2f, %.2f, %.2f) with delay %d (not yet implemented)",
|
||||
spawn.GetName(), x, y, z, delay)
|
||||
return nil
|
||||
}
|
||||
|
||||
// MovementLoopAdd adds a movement loop point
|
||||
func MovementLoopAdd(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
x := ctx.GetParameterFloat("x", 0)
|
||||
y := ctx.GetParameterFloat("y", 0)
|
||||
z := ctx.GetParameterFloat("z", 0)
|
||||
delay := ctx.GetParameterInt("delay", 0)
|
||||
|
||||
// TODO: Implement movement loop system
|
||||
ctx.Debug("Added movement loop point (%.2f, %.2f, %.2f) with delay %d for spawn %s (not yet implemented)",
|
||||
x, y, z, delay, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// PauseMovement pauses the spawn's movement
|
||||
func PauseMovement(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
duration := ctx.GetParameterInt("duration", 0)
|
||||
|
||||
// TODO: Implement movement pausing
|
||||
ctx.Debug("Paused movement for spawn %s for %d ms (not yet implemented)", spawn.GetName(), duration)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopMovement stops the spawn's movement
|
||||
func StopMovement(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement movement stopping
|
||||
ctx.Debug("Stopped movement for spawn %s (not yet implemented)", spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMount sets the spawn's mount
|
||||
func SetMount(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
mountID := ctx.GetParameterInt("mount_id", 0)
|
||||
|
||||
// TODO: Implement mount system
|
||||
ctx.Debug("Set mount %d for spawn %s (not yet implemented)", mountID, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetMount gets the spawn's current mount
|
||||
func GetMount(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement mount system
|
||||
ctx.SetResult("mount_id", 0) // No mount
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetMountColor sets the mount's color
|
||||
func SetMountColor(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
colorR := ctx.GetParameterInt("red", 255)
|
||||
colorG := ctx.GetParameterInt("green", 255)
|
||||
colorB := ctx.GetParameterInt("blue", 255)
|
||||
|
||||
// TODO: Implement mount color system
|
||||
ctx.Debug("Set mount color to RGB(%d, %d, %d) for spawn %s (not yet implemented)",
|
||||
colorR, colorG, colorB, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartAutoMount starts auto-mounting
|
||||
func StartAutoMount(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if !spawn.IsPlayer() {
|
||||
return fmt.Errorf("spawn is not a player")
|
||||
}
|
||||
|
||||
// TODO: Implement auto-mount system
|
||||
ctx.Debug("Started auto-mount for player %s (not yet implemented)", spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// EndAutoMount ends auto-mounting
|
||||
func EndAutoMount(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if !spawn.IsPlayer() {
|
||||
return fmt.Errorf("spawn is not a player")
|
||||
}
|
||||
|
||||
// TODO: Implement auto-mount system
|
||||
ctx.Debug("Ended auto-mount for player %s (not yet implemented)", spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsOnAutoMount checks if spawn is on auto-mount
|
||||
func IsOnAutoMount(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
// TODO: Implement auto-mount system
|
||||
ctx.SetResult("is_on_auto_mount", false)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddWaypoint adds a waypoint for the player
|
||||
func AddWaypoint(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if !spawn.IsPlayer() {
|
||||
return fmt.Errorf("spawn is not a player")
|
||||
}
|
||||
|
||||
x := ctx.GetParameterFloat("x", 0)
|
||||
y := ctx.GetParameterFloat("y", 0)
|
||||
z := ctx.GetParameterFloat("z", 0)
|
||||
waypointName := ctx.GetParameterString("name", "Waypoint")
|
||||
|
||||
// TODO: Implement waypoint system
|
||||
ctx.Debug("Added waypoint '%s' at (%.2f, %.2f, %.2f) for player %s (not yet implemented)",
|
||||
waypointName, x, y, z, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveWaypoint removes a waypoint for the player
|
||||
func RemoveWaypoint(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if !spawn.IsPlayer() {
|
||||
return fmt.Errorf("spawn is not a player")
|
||||
}
|
||||
|
||||
waypointID := ctx.GetParameterInt("waypoint_id", 0)
|
||||
|
||||
// TODO: Implement waypoint system
|
||||
ctx.Debug("Removed waypoint %d for player %s (not yet implemented)", waypointID, spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// SendWaypoints sends waypoints to the player
|
||||
func SendWaypoints(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if !spawn.IsPlayer() {
|
||||
return fmt.Errorf("spawn is not a player")
|
||||
}
|
||||
|
||||
// TODO: Implement waypoint system
|
||||
ctx.Debug("Sent waypoints to player %s (not yet implemented)", spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Evac evacuates the player to safety
|
||||
func Evac(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if !spawn.IsPlayer() {
|
||||
return fmt.Errorf("spawn is not a player")
|
||||
}
|
||||
|
||||
// TODO: Implement evacuation to safe location
|
||||
ctx.Debug("Evacuated player %s to safety (not yet implemented)", spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Bind binds the player to current location
|
||||
func Bind(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if !spawn.IsPlayer() {
|
||||
return fmt.Errorf("spawn is not a player")
|
||||
}
|
||||
|
||||
// TODO: Implement bind system
|
||||
ctx.Debug("Bound player %s to current location (not yet implemented)", spawn.GetName())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Gate gates the player to bind location
|
||||
func Gate(ctx *events.EventContext) error {
|
||||
spawn := ctx.GetSpawn()
|
||||
if spawn == nil {
|
||||
return fmt.Errorf("no spawn in context")
|
||||
}
|
||||
|
||||
if !spawn.IsPlayer() {
|
||||
return fmt.Errorf("spawn is not a player")
|
||||
}
|
||||
|
||||
// TODO: Implement gate system
|
||||
ctx.Debug("Gated player %s to bind location (not yet implemented)", spawn.GetName())
|
||||
return nil
|
||||
}
|
255
internal/events/functions/registry.go
Normal file
255
internal/events/functions/registry.go
Normal file
@ -0,0 +1,255 @@
|
||||
package functions
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"eq2emu/internal/events"
|
||||
)
|
||||
|
||||
// RegisterAllEQ2Functions registers all EQ2 event functions with a handler
|
||||
func RegisterAllEQ2Functions(handler *events.EventHandler) error {
|
||||
functions := map[string]events.EventFunction{
|
||||
// Health Functions
|
||||
"SetCurrentHP": SetCurrentHP,
|
||||
"SetMaxHP": SetMaxHP,
|
||||
"SetMaxHPBase": SetMaxHPBase,
|
||||
"SetCurrentPower": SetCurrentPower,
|
||||
"SetMaxPower": SetMaxPower,
|
||||
"SetMaxPowerBase": SetMaxPowerBase,
|
||||
"ModifyMaxHP": ModifyMaxHP,
|
||||
"ModifyMaxPower": ModifyMaxPower,
|
||||
"ModifyPower": ModifyPower,
|
||||
"ModifyHP": ModifyHP,
|
||||
"ModifyTotalHP": ModifyTotalHP,
|
||||
"ModifyTotalPower": ModifyTotalPower,
|
||||
"GetCurrentHP": GetCurrentHP,
|
||||
"GetMaxHP": GetMaxHP,
|
||||
"GetMaxHPBase": GetMaxHPBase,
|
||||
"GetCurrentPower": GetCurrentPower,
|
||||
"GetMaxPower": GetMaxPower,
|
||||
"GetMaxPowerBase": GetMaxPowerBase,
|
||||
"GetPCTOfHP": GetPCTOfHP,
|
||||
"GetPCTOfPower": GetPCTOfPower,
|
||||
"SpellHeal": SpellHeal,
|
||||
"SpellHealPct": SpellHealPct,
|
||||
"IsAlive": IsAlive,
|
||||
|
||||
// Attributes Functions
|
||||
"SetInt": SetInt,
|
||||
"SetWis": SetWis,
|
||||
"SetSta": SetSta,
|
||||
"SetStr": SetStr,
|
||||
"SetAgi": SetAgi,
|
||||
"SetIntBase": SetIntBase,
|
||||
"SetWisBase": SetWisBase,
|
||||
"SetStaBase": SetStaBase,
|
||||
"SetStrBase": SetStrBase,
|
||||
"SetAgiBase": SetAgiBase,
|
||||
"GetInt": GetInt,
|
||||
"GetWis": GetWis,
|
||||
"GetSta": GetSta,
|
||||
"GetStr": GetStr,
|
||||
"GetAgi": GetAgi,
|
||||
"GetIntBase": GetIntBase,
|
||||
"GetWisBase": GetWisBase,
|
||||
"GetStaBase": GetStaBase,
|
||||
"GetStrBase": GetStrBase,
|
||||
"GetAgiBase": GetAgiBase,
|
||||
"GetLevel": GetLevel,
|
||||
"SetLevel": SetLevel,
|
||||
"SetPlayerLevel": SetPlayerLevel,
|
||||
"GetDifficulty": GetDifficulty,
|
||||
"AddSpellBonus": AddSpellBonus,
|
||||
"RemoveSpellBonus": RemoveSpellBonus,
|
||||
"AddSkillBonus": AddSkillBonus,
|
||||
"RemoveSkillBonus": RemoveSkillBonus,
|
||||
"GetClass": GetClass,
|
||||
"SetClass": SetClass,
|
||||
"SetAdventureClass": SetAdventureClass,
|
||||
"GetTradeskillClass": GetTradeskillClass,
|
||||
"SetTradeskillClass": SetTradeskillClass,
|
||||
"GetTradeskillLevel": GetTradeskillLevel,
|
||||
"SetTradeskillLevel": SetTradeskillLevel,
|
||||
"GetRace": GetRace,
|
||||
"GetGender": GetGender,
|
||||
"GetModelType": GetModelType,
|
||||
"SetModelType": SetModelType,
|
||||
"GetDeity": GetDeity,
|
||||
"SetDeity": SetDeity,
|
||||
"GetAlignment": GetAlignment,
|
||||
"SetAlignment": SetAlignment,
|
||||
|
||||
// Movement Functions
|
||||
"SetPosition": SetPosition,
|
||||
"GetPosition": GetPosition,
|
||||
"GetX": GetX,
|
||||
"GetY": GetY,
|
||||
"GetZ": GetZ,
|
||||
"GetHeading": GetHeading,
|
||||
"SetHeading": SetHeading,
|
||||
"GetOrigX": GetOrigX,
|
||||
"GetOrigY": GetOrigY,
|
||||
"GetOrigZ": GetOrigZ,
|
||||
"GetDistance": GetDistance,
|
||||
"FaceTarget": FaceTarget,
|
||||
"GetSpeed": GetSpeed,
|
||||
"SetSpeed": SetSpeed,
|
||||
"SetSpeedMultiplier": SetSpeedMultiplier,
|
||||
"HasMoved": HasMoved,
|
||||
"IsRunning": IsRunning,
|
||||
"MoveToLocation": MoveToLocation,
|
||||
"ClearRunningLocations": ClearRunningLocations,
|
||||
"SpawnMove": SpawnMove,
|
||||
"MovementLoopAdd": MovementLoopAdd,
|
||||
"PauseMovement": PauseMovement,
|
||||
"StopMovement": StopMovement,
|
||||
"SetMount": SetMount,
|
||||
"GetMount": GetMount,
|
||||
"SetMountColor": SetMountColor,
|
||||
"StartAutoMount": StartAutoMount,
|
||||
"EndAutoMount": EndAutoMount,
|
||||
"IsOnAutoMount": IsOnAutoMount,
|
||||
"AddWaypoint": AddWaypoint,
|
||||
"RemoveWaypoint": RemoveWaypoint,
|
||||
"SendWaypoints": SendWaypoints,
|
||||
"Evac": Evac,
|
||||
"Bind": Bind,
|
||||
"Gate": Gate,
|
||||
|
||||
// Combat Functions
|
||||
"Attack": Attack,
|
||||
"AddHate": AddHate,
|
||||
"ClearHate": ClearHate,
|
||||
"GetMostHated": GetMostHated,
|
||||
"SetTarget": SetTarget,
|
||||
"GetTarget": GetTarget,
|
||||
"IsInCombat": IsInCombat,
|
||||
"SetInCombat": SetInCombat,
|
||||
"SpellDamage": SpellDamage,
|
||||
"SpellDamageExt": SpellDamageExt,
|
||||
"DamageSpawn": DamageSpawn,
|
||||
"ProcDamage": ProcDamage,
|
||||
"ProcHate": ProcHate,
|
||||
"Knockback": Knockback,
|
||||
"Interrupt": Interrupt,
|
||||
"IsCasting": IsCasting,
|
||||
"HasRecovered": HasRecovered,
|
||||
"ProcessMelee": ProcessMelee,
|
||||
"ProcessSpell": ProcessSpell,
|
||||
"LastSpellAttackHit": LastSpellAttackHit,
|
||||
"IsBehind": IsBehind,
|
||||
"IsFlanking": IsFlanking,
|
||||
"InFront": InFront,
|
||||
"GetEncounterSize": GetEncounterSize,
|
||||
"GetEncounter": GetEncounter,
|
||||
"GetHateList": GetHateList,
|
||||
"ClearEncounter": ClearEncounter,
|
||||
"ClearRunback": ClearRunback,
|
||||
"Runback": Runback,
|
||||
"GetRunbackDistance": GetRunbackDistance,
|
||||
"CompareSpawns": CompareSpawns,
|
||||
"KillSpawn": KillSpawn,
|
||||
"KillSpawnByDistance": KillSpawnByDistance,
|
||||
"Resurrect": Resurrect,
|
||||
"IsInvulnerable": IsInvulnerable,
|
||||
"SetInvulnerable": SetInvulnerable,
|
||||
"SetAttackable": SetAttackable,
|
||||
|
||||
// Miscellaneous Functions
|
||||
"SendMessage": SendMessage,
|
||||
"LogMessage": LogMessage,
|
||||
"MakeRandomInt": MakeRandomInt,
|
||||
"MakeRandomFloat": MakeRandomFloat,
|
||||
"ParseInt": ParseInt,
|
||||
"GetName": GetName,
|
||||
"GetID": GetID,
|
||||
"GetSpawnID": GetSpawnID,
|
||||
"IsPlayer": IsPlayer,
|
||||
"IsNPC": IsNPC,
|
||||
"IsEntity": IsEntity,
|
||||
"IsDead": IsDead,
|
||||
"GetCharacterID": GetCharacterID,
|
||||
"Despawn": Despawn,
|
||||
"Spawn": Spawn,
|
||||
"SpawnByLocationID": SpawnByLocationID,
|
||||
"SpawnGroupByID": SpawnGroupByID,
|
||||
"DespawnByLocationID": DespawnByLocationID,
|
||||
"GetSpawnByLocationID": GetSpawnByLocationID,
|
||||
"GetSpawnByGroupID": GetSpawnByGroupID,
|
||||
"GetSpawnGroupID": GetSpawnGroupID,
|
||||
"SetSpawnGroupID": SetSpawnGroupID,
|
||||
"GetSpawnLocationID": GetSpawnLocationID,
|
||||
"GetSpawnLocationPlacementID": GetSpawnLocationPlacementID,
|
||||
"SetGridID": SetGridID,
|
||||
"SpawnSet": SpawnSet,
|
||||
"SpawnSetByDistance": SpawnSetByDistance,
|
||||
"IsSpawnGroupAlive": IsSpawnGroupAlive,
|
||||
"AddSpawnToGroup": AddSpawnToGroup,
|
||||
"GetVariableValue": GetVariableValue,
|
||||
"SetServerVariable": SetServerVariable,
|
||||
"GetServerVariable": GetServerVariable,
|
||||
"SetTempVariable": SetTempVariable,
|
||||
"GetTempVariable": GetTempVariable,
|
||||
"CheckLOS": CheckLOS,
|
||||
"CheckLOSByCoordinates": CheckLOSByCoordinates,
|
||||
}
|
||||
|
||||
for name, fn := range functions {
|
||||
if err := handler.Register(name, fn); err != nil {
|
||||
return fmt.Errorf("failed to register event %s: %w", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetFunctionsByDomain returns functions organized by domain
|
||||
func GetFunctionsByDomain() map[string][]string {
|
||||
return map[string][]string{
|
||||
"health": {
|
||||
"SetCurrentHP", "SetMaxHP", "SetMaxHPBase", "SetCurrentPower", "SetMaxPower",
|
||||
"SetMaxPowerBase", "ModifyMaxHP", "ModifyMaxPower", "ModifyPower", "ModifyHP",
|
||||
"ModifyTotalHP", "ModifyTotalPower", "GetCurrentHP", "GetMaxHP", "GetMaxHPBase",
|
||||
"GetCurrentPower", "GetMaxPower", "GetMaxPowerBase", "GetPCTOfHP", "GetPCTOfPower",
|
||||
"SpellHeal", "SpellHealPct", "IsAlive",
|
||||
},
|
||||
"attributes": {
|
||||
"SetInt", "SetWis", "SetSta", "SetStr", "SetAgi", "SetIntBase", "SetWisBase",
|
||||
"SetStaBase", "SetStrBase", "SetAgiBase", "GetInt", "GetWis", "GetSta", "GetStr",
|
||||
"GetAgi", "GetIntBase", "GetWisBase", "GetStaBase", "GetStrBase", "GetAgiBase",
|
||||
"GetLevel", "SetLevel", "SetPlayerLevel", "GetDifficulty", "AddSpellBonus",
|
||||
"RemoveSpellBonus", "AddSkillBonus", "RemoveSkillBonus", "GetClass", "SetClass",
|
||||
"SetAdventureClass", "GetTradeskillClass", "SetTradeskillClass", "GetTradeskillLevel",
|
||||
"SetTradeskillLevel", "GetRace", "GetGender", "GetModelType", "SetModelType",
|
||||
"GetDeity", "SetDeity", "GetAlignment", "SetAlignment",
|
||||
},
|
||||
"movement": {
|
||||
"SetPosition", "GetPosition", "GetX", "GetY", "GetZ", "GetHeading", "SetHeading",
|
||||
"GetOrigX", "GetOrigY", "GetOrigZ", "GetDistance", "FaceTarget", "GetSpeed",
|
||||
"SetSpeed", "SetSpeedMultiplier", "HasMoved", "IsRunning", "MoveToLocation",
|
||||
"ClearRunningLocations", "SpawnMove", "MovementLoopAdd", "PauseMovement",
|
||||
"StopMovement", "SetMount", "GetMount", "SetMountColor", "StartAutoMount",
|
||||
"EndAutoMount", "IsOnAutoMount", "AddWaypoint", "RemoveWaypoint", "SendWaypoints",
|
||||
"Evac", "Bind", "Gate",
|
||||
},
|
||||
"combat": {
|
||||
"Attack", "AddHate", "ClearHate", "GetMostHated", "SetTarget", "GetTarget",
|
||||
"IsInCombat", "SetInCombat", "SpellDamage", "SpellDamageExt", "DamageSpawn",
|
||||
"ProcDamage", "ProcHate", "Knockback", "Interrupt", "IsCasting", "HasRecovered",
|
||||
"ProcessMelee", "ProcessSpell", "LastSpellAttackHit", "IsBehind", "IsFlanking",
|
||||
"InFront", "GetEncounterSize", "GetEncounter", "GetHateList", "ClearEncounter",
|
||||
"ClearRunback", "Runback", "GetRunbackDistance", "CompareSpawns", "KillSpawn",
|
||||
"KillSpawnByDistance", "Resurrect", "IsInvulnerable", "SetInvulnerable", "SetAttackable",
|
||||
},
|
||||
"misc": {
|
||||
"SendMessage", "LogMessage", "MakeRandomInt", "MakeRandomFloat", "ParseInt",
|
||||
"GetName", "GetID", "GetSpawnID", "IsPlayer", "IsNPC", "IsEntity", "IsDead",
|
||||
"GetCharacterID", "Despawn", "Spawn", "SpawnByLocationID", "SpawnGroupByID",
|
||||
"DespawnByLocationID", "GetSpawnByLocationID", "GetSpawnByGroupID", "GetSpawnGroupID",
|
||||
"SetSpawnGroupID", "GetSpawnLocationID", "GetSpawnLocationPlacementID", "SetGridID",
|
||||
"SpawnSet", "SpawnSetByDistance", "IsSpawnGroupAlive", "AddSpawnToGroup",
|
||||
"GetVariableValue", "SetServerVariable", "GetServerVariable", "SetTempVariable",
|
||||
"GetTempVariable", "CheckLOS", "CheckLOSByCoordinates",
|
||||
},
|
||||
}
|
||||
}
|
82
internal/events/handler.go
Normal file
82
internal/events/handler.go
Normal file
@ -0,0 +1,82 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewEventHandler creates a new event handler
|
||||
func NewEventHandler() *EventHandler {
|
||||
return &EventHandler{
|
||||
events: make(map[string]EventFunction),
|
||||
}
|
||||
}
|
||||
|
||||
// Register registers an event function
|
||||
func (h *EventHandler) Register(eventName string, fn EventFunction) error {
|
||||
if fn == nil {
|
||||
return fmt.Errorf("event function cannot be nil")
|
||||
}
|
||||
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
h.events[eventName] = fn
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unregister removes an event function
|
||||
func (h *EventHandler) Unregister(eventName string) {
|
||||
h.mutex.Lock()
|
||||
defer h.mutex.Unlock()
|
||||
|
||||
delete(h.events, eventName)
|
||||
}
|
||||
|
||||
// Execute executes an event function
|
||||
func (h *EventHandler) Execute(ctx *EventContext) error {
|
||||
if ctx == nil {
|
||||
return fmt.Errorf("context cannot be nil")
|
||||
}
|
||||
|
||||
h.mutex.RLock()
|
||||
fn, exists := h.events[ctx.EventName]
|
||||
h.mutex.RUnlock()
|
||||
|
||||
if !exists {
|
||||
return fmt.Errorf("event %s not found", ctx.EventName)
|
||||
}
|
||||
|
||||
// Set up context with timeout if needed
|
||||
if ctx.Context == nil {
|
||||
timeout, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
ctx.Context = timeout
|
||||
}
|
||||
|
||||
// Execute the function
|
||||
return fn(ctx)
|
||||
}
|
||||
|
||||
// HasEvent checks if an event is registered
|
||||
func (h *EventHandler) HasEvent(eventName string) bool {
|
||||
h.mutex.RLock()
|
||||
defer h.mutex.RUnlock()
|
||||
|
||||
_, exists := h.events[eventName]
|
||||
return exists
|
||||
}
|
||||
|
||||
// ListEvents returns all registered event names
|
||||
func (h *EventHandler) ListEvents() []string {
|
||||
h.mutex.RLock()
|
||||
defer h.mutex.RUnlock()
|
||||
|
||||
events := make([]string, 0, len(h.events))
|
||||
for name := range h.events {
|
||||
events = append(events, name)
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
82
internal/events/types.go
Normal file
82
internal/events/types.go
Normal file
@ -0,0 +1,82 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"eq2emu/internal/entity"
|
||||
"eq2emu/internal/quests"
|
||||
"eq2emu/internal/spawn"
|
||||
)
|
||||
|
||||
// EventType represents different types of events
|
||||
type EventType int
|
||||
|
||||
const (
|
||||
EventTypeSpell EventType = iota
|
||||
EventTypeSpawn
|
||||
EventTypeQuest
|
||||
EventTypeCombat
|
||||
EventTypeZone
|
||||
EventTypeItem
|
||||
)
|
||||
|
||||
func (et EventType) String() string {
|
||||
switch et {
|
||||
case EventTypeSpell:
|
||||
return "spell"
|
||||
case EventTypeSpawn:
|
||||
return "spawn"
|
||||
case EventTypeQuest:
|
||||
return "quest"
|
||||
case EventTypeCombat:
|
||||
return "combat"
|
||||
case EventTypeZone:
|
||||
return "zone"
|
||||
case EventTypeItem:
|
||||
return "item"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// EventContext provides context for event handling
|
||||
type EventContext struct {
|
||||
// Core context
|
||||
Context context.Context
|
||||
|
||||
// Event information
|
||||
EventType EventType
|
||||
EventName string
|
||||
FunctionName string
|
||||
|
||||
// Game objects (nil if not applicable)
|
||||
Caster *entity.Entity
|
||||
Target *entity.Entity
|
||||
Spawn *spawn.Spawn
|
||||
Quest *quests.Quest
|
||||
|
||||
// Parameters and results
|
||||
Parameters map[string]interface{}
|
||||
Results map[string]interface{}
|
||||
|
||||
// Synchronization
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// EventFunction represents a callable event function
|
||||
type EventFunction func(ctx *EventContext) error
|
||||
|
||||
// EventHandler manages event registration and execution
|
||||
type EventHandler struct {
|
||||
events map[string]EventFunction
|
||||
mutex sync.RWMutex
|
||||
}
|
||||
|
||||
// EventLogger provides logging for events
|
||||
type EventLogger interface {
|
||||
Debug(msg string, args ...interface{})
|
||||
Info(msg string, args ...interface{})
|
||||
Warn(msg string, args ...interface{})
|
||||
Error(msg string, args ...interface{})
|
||||
}
|
@ -637,6 +637,70 @@ func (s *Spawn) SetLevel(level int16) {
|
||||
s.addChangedZoneSpawn()
|
||||
}
|
||||
|
||||
// GetClass returns the spawn's adventure class
|
||||
func (s *Spawn) GetClass() int8 {
|
||||
return s.appearance.AdventureClass
|
||||
}
|
||||
|
||||
// SetClass updates the spawn's adventure class and marks info as changed
|
||||
func (s *Spawn) SetClass(class int8) {
|
||||
s.updateMutex.Lock()
|
||||
defer s.updateMutex.Unlock()
|
||||
|
||||
s.appearance.AdventureClass = class
|
||||
s.infoChanged.Store(true)
|
||||
s.changed.Store(true)
|
||||
s.addChangedZoneSpawn()
|
||||
}
|
||||
|
||||
// GetTradeskillClass returns the spawn's tradeskill class
|
||||
func (s *Spawn) GetTradeskillClass() int8 {
|
||||
return s.appearance.TradeskillClass
|
||||
}
|
||||
|
||||
// SetTradeskillClass updates the spawn's tradeskill class and marks info as changed
|
||||
func (s *Spawn) SetTradeskillClass(class int8) {
|
||||
s.updateMutex.Lock()
|
||||
defer s.updateMutex.Unlock()
|
||||
|
||||
s.appearance.TradeskillClass = class
|
||||
s.infoChanged.Store(true)
|
||||
s.changed.Store(true)
|
||||
s.addChangedZoneSpawn()
|
||||
}
|
||||
|
||||
// GetRace returns the spawn's race
|
||||
func (s *Spawn) GetRace() int8 {
|
||||
return s.appearance.Race
|
||||
}
|
||||
|
||||
// SetRace updates the spawn's race and marks info as changed
|
||||
func (s *Spawn) SetRace(race int8) {
|
||||
s.updateMutex.Lock()
|
||||
defer s.updateMutex.Unlock()
|
||||
|
||||
s.appearance.Race = race
|
||||
s.infoChanged.Store(true)
|
||||
s.changed.Store(true)
|
||||
s.addChangedZoneSpawn()
|
||||
}
|
||||
|
||||
// GetGender returns the spawn's gender
|
||||
func (s *Spawn) GetGender() int8 {
|
||||
return s.appearance.Gender
|
||||
}
|
||||
|
||||
// SetGender updates the spawn's gender and marks info as changed
|
||||
func (s *Spawn) SetGender(gender int8) {
|
||||
s.updateMutex.Lock()
|
||||
defer s.updateMutex.Unlock()
|
||||
|
||||
s.appearance.Gender = gender
|
||||
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
|
||||
|
Loading…
x
Reference in New Issue
Block a user