Compare commits

..

2 Commits

Author SHA1 Message Date
abec68872f migrate commands package 2025-08-06 22:13:32 -05:00
8bc92f035a upload C++ source 2025-08-06 19:00:30 -05:00
239 changed files with 191056 additions and 653 deletions

5
.gitignore vendored
View File

@ -16,7 +16,4 @@
*.out
# Go workspace file
go.work
# LuaJIT wrapper source
luawrapper
go.work

557
CLAUDE.md
View File

@ -2,482 +2,7 @@
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
EQ2Go is a Go rewrite of the EverQuest II server emulator from C++ EQ2EMu. Implements core MMO server functionality with authentication, world simulation, and game mechanics using modern Go practices.
## Development Commands
### Building the Project
```bash
# Build login server
go build -o bin/login_server ./cmd/login_server
# Build world server
go build -o bin/world_server ./cmd/world_server
# Build both servers
go build ./cmd/...
```
### Running the Servers
```bash
# Run login server (requires login_config.json)
./bin/login_server
# Run world server (creates world_config.json with defaults if missing)
./bin/world_server
# With custom configuration
./bin/world_server -listen-port 9001 -web-port 8081 -log-level debug
```
### Testing
```bash
# Run all tests
go test ./...
# Test specific packages
go test ./internal/udp
go test ./internal/packets/parser
# Run tests with verbose output
go test -v ./...
# Test with race detection
go test -race ./...
```
### Development Tools
```bash
# Format code
go fmt ./...
# Run linter (if golangci-lint is installed)
golangci-lint run
# Tidy modules
go mod tidy
# Check for vulnerabilities
go run golang.org/x/vuln/cmd/govulncheck@latest ./...
```
## Architecture Overview
### Core Components
**Login Server** - Client authentication, character management, world server coordination
**World Server** - Game simulation engine, zones, NPCs, combat, quests, web admin interface
**Shared Libraries:**
- `common/` - EQ2-specific data types and constants
- `database/` - SQLite wrapper with simplified query interface
- `udp/` - Custom UDP protocol with reliability layer
- `packets/` - XML-driven packet definition parser
- `achievements/` - Achievement system
- `spawn/` - Base game entity system (positioning, commands, scripting)
- `entity/` - Combat-capable entities with spell effects, stats, pet management
- `spells/` - Complete spell system with casting mechanics and processing
- `titles/` - Character title system with achievement integration
- `trade/` - Player trading system with item/coin exchange and validation
- `object/` - Interactive objects extending spawn system (merchants, transporters, devices, collectors)
- `races/` - Race system with all EQ2 races, alignment classification, stat modifiers, and entity integration
- `classes/` - Class system with all EQ2 classes, progression paths, stat bonuses, and entity integration
- `widget/` - Interactive widget system for doors, lifts, and other usable objects with spawn integration
- `transmute/` - Item transmutation system for converting items into crafting materials with skill requirements
- `skills/` - Character skill system with master skill list, player skills, bonuses, and progression mechanics
- `sign/` - Interactive sign system extending spawn functionality with zone transport, entity commands, and text display
- `appearances/` - Appearance management system with client version compatibility and efficient ID-based lookups
- `factions/` - Faction reputation system with player standings, consideration levels, and inter-faction relationships
- `ground_spawn/` - Harvestable resource node system with skill-based harvesting, rare item generation, and multi-attempt mechanics
- `languages/` - Multilingual character communication system with master language registry and player-specific language knowledge
### Network Protocol
EverQuest II UDP protocol with reliability layer, RC4 encryption, CRC validation, connection management, packet combining.
### Database Layer
SQLite with simplified query interface, transactions, connection pooling, parameter binding.
### Packet System
XML-driven packet definitions with version-specific formats, conditional fields, templates, dynamic arrays.
## Key Files and Locations
**Server Entry Points:**
- `cmd/login_server/main.go`: Login server startup and configuration
- `cmd/world_server/main.go`: World server startup with command-line options
**Core Data Structures:**
- `internal/common/types.go`: EQ2-specific types (EQ2Color, EQ2Equipment, AppearanceData, etc.)
- `internal/common/visual_states.go`: Visual states, emotes, and spell visuals with version management
- `internal/common/variables.go`: Configuration variable management with type conversion utilities
**Network Implementation:**
- `internal/udp/server.go`: Multi-connection UDP server
- `internal/udp/connection.go`: Individual client connection handling
- `internal/udp/protocol.go`: Protocol packet types and constants
**Database:**
- `internal/database/wrapper.go`: Simplified SQLite interface
- Database files: `login.db`, `world.db` (created automatically)
**Spawn System:**
- `internal/spawn/spawn.go`: Base spawn entity with position, stats, commands, and scripting
- `internal/spawn/spawn_lists.go`: Spawn location and entry management
- `internal/spawn/README.md`: Comprehensive spawn system documentation
**Entity System:**
- `internal/entity/entity.go`: Combat-capable spawn extension with spell casting and pet management
- `internal/entity/info_struct.go`: Comprehensive character statistics and information management
- `internal/entity/spell_effects.go`: DEPRECATED - now imports from spells package
- `internal/entity/README.md`: Complete entity system documentation
**Spells System:**
- Core: `spell_data.go`, `spell.go`, `spell_effects.go`, `spell_manager.go`, `constants.go`
- Processing: `process_constants.go`, `spell_process.go`, `spell_targeting.go`, `spell_resources.go`
- Docs: `README.md`, `SPELL_PROCESS.md`
**Titles System:**
- `internal/titles/title.go`: Core title data structures and player title management
- `internal/titles/constants.go`: Title system constants, categories, and rarity levels
- `internal/titles/master_list.go`: Global title registry and management with categorization
- `internal/titles/player_titles.go`: Individual player title collections and active title tracking
- `internal/titles/title_manager.go`: Central title system coordinator with background cleanup
- `internal/titles/integration.go`: Integration systems for earning titles through achievements, quests, PvP, events
- `internal/titles/README.md`: Complete title system documentation
**Trade System:**
- `internal/trade/trade.go`: Core trade mechanics with two-party item/coin exchange
- `internal/trade/types.go`: Trade data structures, participants, and validation
- `internal/trade/constants.go`: Trade constants, error codes, and configuration
- `internal/trade/utils.go`: Coin calculations, validation helpers, and formatting
- `internal/trade/manager.go`: Trade service with high-level management and placeholders
**Object System:**
- `internal/object/object.go`: Core object mechanics extending spawn functionality with interaction
- `internal/object/constants.go`: Object constants, spawn types, and interaction types
- `internal/object/manager.go`: Object manager with zone-based and type-based indexing
- `internal/object/integration.go`: Spawn system integration with ObjectSpawn wrapper
- `internal/object/interfaces.go`: Integration interfaces for trade/spell systems and entity adapters
**Race System:**
- `internal/races/races.go`: Core race management with all 21 EQ2 races and alignment mapping
- `internal/races/constants.go`: Race IDs, names, and display constants
- `internal/races/utils.go`: Race utilities with stat modifiers, descriptions, and parsing
- `internal/races/integration.go`: Entity system integration with RaceAware interface
- `internal/races/manager.go`: High-level race management with statistics and command processing
**Class System:**
- `internal/classes/classes.go`: Core class management with all EQ2 adventure and tradeskill classes
- `internal/classes/constants.go`: Class IDs, names, and display constants with progression hierarchy
- `internal/classes/utils.go`: Class utilities with progression paths, stat bonuses, and parsing
- `internal/classes/integration.go`: Entity system integration with ClassAware interface
- `internal/classes/manager.go`: High-level class management with statistics and command processing
**Widget System:**
- `internal/widget/widget.go`: Interactive spawn objects like doors and lifts with movement and state management
- `internal/widget/constants.go`: Widget type constants and display name mappings
- `internal/widget/actions.go`: Widget interaction logic with open/close mechanics and client handling
- `internal/widget/interfaces.go`: Integration interfaces for client and zone systems with spawn wrapper
- `internal/widget/manager.go`: Widget management with timer handling and linked spawn resolution
**Transmute System:**
- `internal/transmute/transmute.go`: Core transmutation logic with item validation and material generation
- `internal/transmute/types.go`: Transmute data structures and interface definitions for system integration
- `internal/transmute/constants.go`: Transmutation constants including probabilities and item flags
- `internal/transmute/manager.go`: High-level transmute management with statistics and command processing
- `internal/transmute/database.go`: Database operations for transmuting tier configuration
- `internal/transmute/packet_builder.go`: Client packet construction for transmutation UI and responses
**Skills System:**
- `internal/skills/types.go`: Core skill data structures with Skill, SkillBonus, and SkillBonusValue types
- `internal/skills/constants.go`: Skill type constants, special skill IDs, and skill increase parameters
- `internal/skills/master_skill_list.go`: Master skill registry with all available skills and packet building
- `internal/skills/player_skill_list.go`: Individual player skill management with values, caps, and updates
- `internal/skills/skill_bonuses.go`: Skill bonus system for spell-based skill modifications and calculations
- `internal/skills/manager.go`: High-level skill management with statistics, validation, and command processing
- `internal/skills/integration.go`: Integration interfaces including SkillAware and EntitySkillAdapter
**Sign System:**
- `internal/sign/types.go`: Core Sign struct extending spawn functionality with widget and zone transport properties
- `internal/sign/constants.go`: Sign type constants, default values, and configuration parameters
- `internal/sign/sign.go`: Sign functionality including copy, serialization, usage handling, and validation
- `internal/sign/interfaces.go`: Integration interfaces with SignAware, SignAdapter, and system dependencies
- `internal/sign/manager.go`: Sign management with zone loading, statistics, validation, and command processing
**Appearances System:**
- `internal/appearances/types.go`: Core Appearance struct with ID, name, and client version compatibility
- `internal/appearances/constants.go`: Hash search constants and client version parameters
- `internal/appearances/appearances.go`: Appearance collection management with thread-safe operations
- `internal/appearances/manager.go`: High-level appearance management with database integration and statistics
- `internal/appearances/interfaces.go`: Integration interfaces with caching, entity adapters, and system dependencies
**Factions System:**
- `internal/factions/types.go`: Core Faction struct with reputation properties and validation methods
- `internal/factions/constants.go`: Faction value limits, consideration levels, and calculation constants
- `internal/factions/master_faction_list.go`: Master faction registry with hostile/friendly relationships
- `internal/factions/player_faction.go`: Individual player faction standings with consideration and percentage calculations
- `internal/factions/manager.go`: High-level faction management with statistics, validation, and command processing
- `internal/factions/interfaces.go`: Integration interfaces with entity adapters, player managers, and system dependencies
**Ground Spawn System:**
- `internal/ground_spawn/constants.go`: Harvest type constants, skill names, rarity flags, and configuration defaults
- `internal/ground_spawn/types.go`: Core GroundSpawn struct, harvest context data, result structures, and statistics tracking
- `internal/ground_spawn/ground_spawn.go`: Core ground spawn functionality with complex harvest processing and skill-based mechanics
- `internal/ground_spawn/interfaces.go`: Integration interfaces with database, players, items, skills, and event handling systems
- `internal/ground_spawn/manager.go`: High-level ground spawn management with respawn scheduling, statistics, and command processing
**Languages System:**
- `internal/languages/constants.go`: Language ID constants, validation limits, and system configuration values
- `internal/languages/types.go`: Core Language struct, master language list, player language list, and statistics tracking
- `internal/languages/manager.go`: High-level language management with database integration, statistics, and command processing
- `internal/languages/interfaces.go`: Integration interfaces with database, players, chat processing, and event handling systems
**Player System:**
- `internal/player/player.go`: Core Player struct extending Entity with character data, experience, skills, spells, combat, social features
- `internal/player/player_info.go`: PlayerInfo struct for detailed character sheet data and serialization
- `internal/player/character_flags.go`: Character flag management for all EQ2 player states (anonymous, AFK, LFG, etc.)
- `internal/player/currency.go`: Coin and currency handling with validation and transaction support
- `internal/player/experience.go`: XP, leveling, and vitality systems with automatic level progression
- `internal/player/combat.go`: Combat mechanics, auto-attack, and weapon handling integration
- `internal/player/quest_management.go`: Quest system integration with tracking, progress, and completion
- `internal/player/spell_management.go`: Spell book and casting management with passive spell support
- `internal/player/skill_management.go`: Skill system integration with progression and bonuses
- `internal/player/spawn_management.go`: Spawn visibility and tracking for player view management
- `internal/player/manager.go`: Multi-player management system with statistics, events, and background processing
- `internal/player/interfaces.go`: System integration interfaces and player adapter for other systems
- `internal/player/database.go`: SQLite database operations for player persistence with zombiezen integration
- `internal/player/README.md`: Complete player system documentation with usage examples
**Quests System:**
- `internal/quests/constants.go`: Quest step types, display status flags, sharing constants, and validation limits
- `internal/quests/types.go`: Core Quest and QuestStep structures with complete quest data management and thread-safe operations
- `internal/quests/quest.go`: Core quest functionality including step management, progress tracking, update checking, and validation
- `internal/quests/prerequisites.go`: Quest prerequisite management with class, race, level, faction, and quest requirements
- `internal/quests/rewards.go`: Quest reward system with coins, experience, status, faction rewards, and level-based calculations
- `internal/quests/actions.go`: Quest action system for Lua script execution on completion, progress, and failure events
- `internal/quests/manager.go`: MasterQuestList and QuestManager for system-wide quest management and player quest tracking
- `internal/quests/interfaces.go`: Integration interfaces with player, client, spawn, item systems and QuestSystemAdapter for complete quest lifecycle management
**NPC System:**
- `internal/npc/constants.go`: AI strategy constants, randomization flags, pet types, cast types, and system limits
- `internal/npc/types.go`: Core NPC struct extending Entity, NPCSpell configurations, skill bonuses, movement locations, brain system
- `internal/npc/npc.go`: Complete NPC functionality with AI, combat, spell casting, movement, appearance randomization, and validation
- `internal/npc/manager.go`: High-level NPC management with zone indexing, appearance tracking, combat processing, and statistics
- `internal/npc/interfaces.go`: Integration interfaces with database, spell/skill/appearance systems, combat, movement, and entity adapters
**NPC AI System:**
- `internal/npc/ai/constants.go`: AI timing constants, combat ranges, hate limits, brain types, and decision parameters
- `internal/npc/ai/types.go`: HateList and EncounterList management, BrainState tracking, performance statistics, and thread-safe operations
- `internal/npc/ai/brain.go`: BaseBrain with complete AI logic including hate management, encounter tracking, spell/melee processing, and combat decisions
- `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
- `internal/packets/parser/`: Parser implementation for XML packet definitions
## Configuration
**Login Server**: Requires `login_config.json` with database and server settings
**World Server**: Creates `world_config.json` with defaults:
```json
{
"listen_addr": "0.0.0.0",
"listen_port": 9000,
"max_clients": 1000,
"web_port": 8080,
"database_path": "world.db",
"log_level": "info"
}
```
Command-line flags override JSON configuration.
## Important Dependencies
**Core Dependencies:**
- `zombiezen.com/go/sqlite`: Modern SQLite driver
- `golang.org/x/crypto`: Cryptographic functions
**Module Information:**
- Go version: 1.24.5
- Module name: `eq2emu`
## Development Notes
**Architecture Transition**: This Go implementation is based on the extensive C++ EQ2EMu codebase documented in `EQ2EMU_Architecture_White_Paper.md`. The Go version maintains compatibility with the original protocol while modernizing the implementation.
**Packet Handling**: The XML packet definition system allows for easy addition of new packet types without code changes. See `internal/packets/PARSER.md` for syntax reference.
**Database Schema**: Currently uses SQLite for development/testing. Production deployments may require migration to PostgreSQL or MySQL for better concurrent access.
**Spawn System**: The spawn system (`internal/spawn/`) provides the foundation for all game entities. It's converted from the C++ EQ2EMu codebase with modern Go concurrency patterns.
**Entity System**: The entity system (`internal/entity/`) extends spawns with combat capabilities, implementing the complete EverQuest II character statistics system, spell effect management, and pet systems. Key features include:
- **InfoStruct**: Complete character statistics (attributes, resistances, experience, currency, equipment data)
- **Entity**: Combat-capable spawn with spell casting, pet management, and bonus calculations
- **Thread Safety**: All systems use proper Go concurrency patterns with mutexes and atomic operations
- **Stat Calculations**: Dynamic stat calculations with bonuses from equipment, spells, and other sources
- **Pet Management**: Support for multiple pet types (summon, charm, deity, cosmetic) with proper ownership tracking
**Spells System**: Complete spell management with spell definitions, effect system, and real-time casting engine. Features spell processing (50ms intervals), comprehensive targeting (self/single/group/AOE), resource management (power/health/concentration), cast/recast timers, interrupt handling, heroic opportunities, and thread-safe operations.
**Titles System**: Character title management with prefix/suffix positioning, categories, rarity levels, achievement integration, and thread-safe operations.
**Trade System**: Player-to-player trading with item/coin exchange, validation (no-trade, heirloom restrictions), slot management, acceptance workflow, bot trading support, and client version compatibility (6/12 slots).
**Object System**: Interactive world objects extending spawn system with merchants, transporters, devices, and collectors. Features clickable interaction, command handling, device IDs, size randomization, transport/teleport support, and complete spawn integration with entity/item adapters for trade/spell system compatibility.
**Race System**: Complete EverQuest II race management with all 21 races (Human through Aerakyn), alignment classification (good/evil/neutral), racial stat modifiers, starting locations, lore descriptions, and full entity system integration. Features randomization by alignment, compatibility checking, usage statistics, and RaceAware interface for seamless integration with existing systems.
**Class System**: Complete EverQuest II class management with all 58 classes (adventure and tradeskill), hierarchical progression paths (Commoner → Base → Secondary → Final), stat bonuses, starting stat calculations, and full entity system integration. Features class transition validation, progression tracking, usage statistics, and ClassAware interface for seamless integration with existing systems. Includes all 4 base classes (Fighter, Priest, Mage, Scout) with their complete specialization trees.
**Visual States System**: Manages visual animations, emotes, and spell visuals with client version support. Features version-based emote/visual selection, animation ID mapping, message formatting (targeted/untargeted), and thread-safe concurrent access. Supports both named lookups and ID-based lookups for efficient client communication.
**Variables System**: Configuration variable management for runtime settings and game parameters. Features type-safe value conversion (int, float, bool), partial name matching, variable cloning and merging, comment support for documentation, and thread-safe operations. Used for game rules, server settings, and dynamic configuration.
**Widget System**: Interactive spawn objects like doors, lifts, and other usable world elements. Features open/close state management, position-based movement with timers, sound integration, linked widget chains (action/linked spawns), house integration for player housing, multi-floor lift support, and complete spawn system integration. Supports complex interactions like transporter integration and custom scripted behaviors.
**Transmute System**: Item transmutation system allowing players to convert items into crafting materials. Features tier-based material generation, skill requirement validation, probabilistic loot rolls (15% both materials, 75%/25% split), automatic skill progression, request state management, and comprehensive validation. Supports level-based transmuting tiers with four material types (fragments, powder, infusions, mana) and integrates with item, spell, and skill systems.
**Skills System**: Complete character skill system with master skill registry and individual player skill management. Features all EQ2 skill types (weaponry, spellcasting, avoidance, armor, harvesting, artisan, etc.), skill bonuses from spells, skill progression with automatic increases, disarm skill checks for chests, and comprehensive skill value calculations. Supports skill caps, type-based operations, packet building for client updates, and full integration with entity system through SkillAware interface. Includes special handling for weapon skills, crafting skills, and language skills with version-specific client compatibility.
**Sign System**: Interactive sign system extending spawn functionality for in-world text displays and zone transport. Features two sign types (generic and zone transport), zone teleportation with coordinate validation, entity command processing, sign marking system, transporter integration, distance-based interaction, and comprehensive text display with location/heading options. Supports quest requirement checking, instance zone handling, size randomization on copy, validation system, and full spawn system integration. Includes SignAware interface and SignAdapter for seamless integration with existing entity systems.
**Appearances System**: Comprehensive appearance management system handling visual character and entity representations with client version compatibility. Features efficient hash-based ID lookups, client version compatibility checking, name-based searching, statistics tracking, and thread-safe operations. Supports caching with SimpleAppearanceCache and CachedAppearanceManager, database integration for persistence, entity appearance adapters for seamless integration, and comprehensive validation. Includes AppearanceAware interface and EntityAppearanceAdapter for entity system integration. Designed to handle large appearance collections with sparse ID ranges efficiently using hash tables as noted in C++ comments.
**Factions System**: Complete faction reputation system managing player standings and inter-faction relationships. Features master faction list with hostile/friendly relationships, individual player faction standings with consideration levels (-4 to 4), percentage calculations within consideration ranges, attack determination based on faction standing, and comprehensive faction value management (-50000 to 50000 range). Supports special faction handling (IDs <= 10), faction increase/decrease with configurable amounts, packet building for client updates, statistics tracking, and thread-safe operations. Includes EntityFactionAdapter and PlayerFactionManager for seamless integration with entity and player systems. Maintains exact C++ calculation formulas for consideration levels and percentage values.
**Ground Spawn System**: Harvestable resource node system extending spawn functionality for gathering, mining, fishing, trapping, and foresting. Features skill-based table selection, probabilistic harvest outcomes (1/3/5/10 items + rare/imbue types), multi-attempt harvesting sessions, skill progression mechanics, and automatic respawn scheduling. Implements complex C++ harvest logic with table filtering by skill/level requirements, random item selection from filtered pools, grid-based location restrictions, and comprehensive item reward processing. Supports collection vs. harvesting skill differentiation, harvest message generation, spell integration for casting animations, and statistics tracking. Includes PlayerGroundSpawnAdapter and HarvestEventAdapter for seamless integration with player and event systems. Thread-safe operations with separate mutexes for harvest processing and usage handling.
**Languages System**: Multilingual character communication system managing language learning and chat processing. Features master language registry with ID-based and name-based lookups, individual player language collections with thread-safe operations, language validation and persistence, and comprehensive multilingual chat processing. Supports all EverQuest II racial languages (Common, Elvish, Dwarven, Halfling, Gnomish, Iksar, Trollish, Ogrish, Fae, Arasai, Sarnak, Froglok), language learning/forgetting mechanics, primary language selection, and message scrambling for unknown languages. Includes PlayerLanguageAdapter for seamless player integration, ChatLanguageProcessor for multilingual communication, and statistics tracking for language usage patterns. Thread-safe operations with efficient hash-based lookups and comprehensive validation systems.
**Player System**: Complete player character management extending Entity with comprehensive MMO player functionality. Features Player struct with character data (ID, level, class, race), experience systems (adventure/tradeskill XP with automatic leveling), skill progression, spell management (spell books, casting, passive spells), combat mechanics (auto-attack, combat state, weapon handling), social features (friends/ignore lists), currency management (coin transactions with validation), quest integration (tracking, progress, completion), spawn management (visibility, tracking for player view), character flags (anonymous, AFK, LFG, roleplaying, etc.), movement and positioning, housing integration, and comprehensive cleanup systems. Includes PlayerManager for multi-player coordination with statistics, events, background processing (save/update loops), zone management, and thread-safe operations. Database integration uses zombiezen SQLite with complete persistence for all player data. All systems are thread-safe with proper Go concurrency patterns and extensive test coverage including concurrent access testing.
**Quests System**: Complete quest management with quest definitions, step system, and real-time progress tracking. Features quest system with multiple step types (kill, chat, obtain item, location, spell, normal, craft, harvest, kill race requirements), comprehensive prerequisite system (level, class, race, faction, quest dependencies), flexible reward system (coins, experience, status points, faction reputation, items), step-based progress tracking with percentage-based success chances, task group organization for complex quests, Lua action system for completion/progress/failure events, quest sharing system with configurable sharing rules, repeatable quest support, player quest management with active quest tracking, master quest list with categorization and search, validation system for quest integrity, and thread-safe operations with proper mutex usage. Includes QuestSystemAdapter for complete quest lifecycle management and integration with player, client, spawn, and item systems.
**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 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.
**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
- `sqlitex.Execute` with `ExecOptions` and `ResultFunc` for queries
- Proper `found` flag handling to detect when no rows are returned
- Thread-safe operations with connection management
- Complete conversion in player system serves as reference implementation
**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:
- `types.go`: Core data structures and type definitions
- `constants.go`: System constants, IDs, and configuration values
- `manager.go`: High-level system management and coordination
- `interfaces.go`: Integration interfaces and adapter patterns
- `database.go`: Database persistence layer (where applicable)
- `README.md`: Package-specific documentation with usage examples
**Thread Safety**: All systems implement proper Go concurrency patterns:
- `sync.RWMutex` for read-heavy operations (maps, lists)
- `atomic` operations for simple flags and counters
- Proper lock ordering to prevent deadlocks
- Channel-based communication for background processing
**Integration Patterns**:
- `*Aware` interfaces for feature detection and loose coupling
- Adapter pattern for bridging different system interfaces
- Manager pattern for centralized system coordination
- Event-driven architecture for system notifications
**Entity-Pass-By-Pointer**: Always pass `*entity.Entity` (not `entity.Entity`) to avoid copying locks and improve performance. This applies to all entity-related method signatures.
**Name Padding Handling**: Player names from database may have null byte padding from `[128]byte` arrays. Always use `strings.TrimSpace(strings.Trim(name, "\x00"))` when working with player names from database or similar fixed-size string fields.
## Code Documentation Standards
## Code Quality Requirements
**Function Documentation**: All functions must have thorough documentation explaining their purpose, parameters, return values, and any important behavior. Do NOT follow the standard Go convention of starting comments with the function name - instead, write clear explanations of what the function does.
@ -502,4 +27,82 @@ func NewServer(addr string, handler PacketHandler, config ...Config) (*Server, e
- Include examples in documentation for complex functions
- Explain any non-obvious algorithms or business logic
- Document error conditions and edge cases
- For packet-related code, reference the relevant XML definitions or protocol specifications
- For packet-related code, reference the relevant XML definitions or protocol specifications
## Project Overview
EQ2Go is a Go rewrite of the EverQuest II server emulator from C++ EQ2EMu. Implements core MMO server functionality with authentication, world simulation, and game mechanics using modern Go practices.
## Development Commands
```bash
# Build servers
go build ./cmd/...
# Test with race detection
go test -race ./...
# Format and tidy
go fmt ./...; go mod tidy
```
## Architecture Overview
**Login Server** - Client authentication, character management, world server coordination
**World Server** - Game simulation engine, zones, NPCs, combat, quests, web admin interface
**Core Systems:**
- Network: EverQuest II UDP protocol with reliability layer, RC4 encryption, CRC validation
- Database: SQLite with simplified query interface, transactions, connection pooling
- Packets: XML-driven packet definitions with version-specific formats
- Events: 100+ EQ2 functions organized by domain (health/attributes/movement/combat/misc)
## Key Systems
**Spawn System**: Base game entity system with positioning, commands, and scripting. Foundation for all game entities with modern Go concurrency patterns.
**Entity System**: Combat-capable spawns with spell effects, stats, pet management. Includes InfoStruct for complete character statistics, spell casting, pet management, and bonus calculations.
**Spells System**: Complete spell management with 50ms processing intervals, comprehensive targeting (self/single/group/AOE), resource management (power/health/concentration), cast/recast timers, interrupt handling, and heroic opportunities.
**Player System**: Complete player character management extending Entity with experience systems, skill progression, spell management, combat mechanics, social features, currency management, quest integration, spawn management, character flags, and comprehensive cleanup systems. Database integration uses zombiezen SQLite with complete persistence.
**NPC System**: Non-player characters extending Entity with complete AI, combat, and spell casting. Features brain system, spell management, skill bonuses, movement with runback mechanics, appearance randomization, and AI strategies. Includes specialized brain types (Combat/NonCombat/Blank/Lua/DumbFire pets).
**Quest System**: Complete quest management with multiple step types (kill/chat/obtain/location/spell/normal/craft/harvest), comprehensive prerequisite system, flexible reward system, step-based progress tracking, Lua action system, quest sharing, and repeatable quest support.
**Event System**: 137 EQ2 functions organized by domain in `internal/events/functions/` - health.go (23 functions), attributes.go (24 functions), movement.go (27 functions), combat.go (36 functions), misc.go (27 functions). Features EventContext with fluent API, thread-safe operations, minimal overhead, type-safe parameter access, and comprehensive registry system.
**Trade/Object/Widget/Transmute/Skills/Titles/Races/Classes/Languages/Factions/Ground Spawn/Appearances/Sign Systems**: Complete implementations with full EQ2 compatibility, thread-safe operations, and comprehensive integration interfaces.
## Development Patterns
**Package Structure**: Each system follows consistent structure with types.go, constants.go, manager.go, interfaces.go, database.go (where applicable), and README.md.
**Thread Safety**: All systems implement proper Go concurrency patterns with sync.RWMutex for read-heavy operations, atomic operations for flags/counters, proper lock ordering, and channel-based communication.
**Integration Patterns**: *Aware interfaces for feature detection, adapter pattern for bridging systems, manager pattern for coordination, event-driven architecture for notifications.
**Entity-Pass-By-Pointer**: Always pass `*entity.Entity` (not `entity.Entity`) to avoid copying locks and improve performance.
**Name Padding Handling**: Player names from database may have null byte padding from `[128]byte` arrays. Always use `strings.TrimSpace(strings.Trim(name, "\x00"))`.
## Configuration
**World Server** creates `world_config.json` with defaults. Command-line flags override JSON configuration.
## Dependencies
- `zombiezen.com/go/sqlite`: Modern SQLite driver
- `golang.org/x/crypto`: Cryptographic functions
- Go version: 1.24.5, Module: `eq2emu`
## Important Notes
**C++ Reference Code**: The original C++ EQ2EMu source code is available in `/old` directory for functionality reference only. Use this to understand game mechanics, algorithms, and expected behavior - NOT for structure or patterns. The Go implementation uses modern Go practices and patterns.
**Database Migration**: Uses zombiezen SQLite with `sqlite.Conn`, `sqlitex.Execute` with `ExecOptions` and `ResultFunc`, proper `found` flag handling, and thread-safe connection management.
**Architecture Transition**: Converted from C++ EQ2EMu codebase maintaining protocol compatibility while modernizing implementation.
**Testing**: Focus on UDP protocol and packet parsing for client compatibility. All systems include comprehensive test suites with concurrency testing.

327
internal/commands/README.md Normal file
View File

@ -0,0 +1,327 @@
# EQ2Go Command System
The EQ2Go command system provides a comprehensive framework for handling player commands, admin commands, and console commands in the EverQuest II server emulator. This system is converted from the original C++ EQ2EMu codebase while leveraging modern Go patterns and practices.
## Architecture Overview
The command system consists of several key components:
- **CommandManager**: Central registry for all commands with thread-safe operations
- **CommandContext**: Execution context providing access to players, zones, arguments, and messaging
- **Command Types**: Different command categories (Player, Admin, Console, etc.)
- **Subcommand Support**: Hierarchical command structure for complex operations
## Core Components
### CommandManager
The `CommandManager` is the central hub that:
- Registers commands by name with case-insensitive lookup
- Manages subcommands under parent commands
- Executes commands with proper admin level checking
- Parses raw command strings into structured contexts
### CommandContext
The `CommandContext` provides:
- Type-safe argument parsing (string, int, float, bool)
- Message queuing with channel and color support
- Validation helpers for argument counts and requirements
- Access to game objects (player, zone, target)
- Result storage for complex operations
### Command Types
Commands are categorized by type:
- **Player Commands** (`CommandTypePlayer`): Basic player actions like `/say`, `/tell`, `/who`
- **Admin Commands** (`CommandTypeAdmin`): GM/Admin tools like `/kick`, `/ban`, `/summon`
- **Console Commands** (`CommandTypeConsole`): Server management from console
- **Specialized Types**: Spawn, Zone, Guild, Item, and Quest commands
## Admin Levels
The system supports four admin levels:
- **Player** (0): Basic player commands
- **Guide** (100): Helper/guide commands
- **GM** (200): Game master commands
- **Admin** (300): Full administrative access
## Usage Examples
### Basic Command Registration
```go
// Initialize the command system
cm, err := InitializeCommands()
if err != nil {
log.Fatalf("Failed to initialize commands: %v", err)
}
// Register a custom command
customCmd := &Command{
Name: "custom",
Type: CommandTypePlayer,
Description: "A custom command",
Usage: "/custom <arg>",
RequiredLevel: AdminLevelPlayer,
Handler: func(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(1, 1); err != nil {
ctx.AddErrorMessage("Usage: /custom <arg>")
return nil
}
arg := ctx.Arguments[0]
ctx.AddStatusMessage(fmt.Sprintf("You used custom command with: %s", arg))
return nil
},
}
err = cm.Register(customCmd)
if err != nil {
log.Printf("Failed to register custom command: %v", err)
}
```
### Executing Commands
```go
// Parse and execute a command from a client
err := ExecuteCommand(cm, "/say Hello everyone!", client)
if err != nil {
log.Printf("Command execution failed: %v", err)
}
```
### Subcommand Registration
```go
// Register a parent command
parentCmd := &Command{
Name: "manage",
Type: CommandTypeAdmin,
RequiredLevel: AdminLevelGM,
Handler: func(ctx *CommandContext) error {
ctx.AddErrorMessage("Usage: /manage <player|item|zone> [options]")
return nil
},
}
cm.Register(parentCmd)
// Register subcommands
playerSubCmd := &Command{
Name: "player",
Type: CommandTypeAdmin,
RequiredLevel: AdminLevelGM,
Handler: func(ctx *CommandContext) error {
// Handle player management
return nil
},
}
cm.RegisterSubCommand("manage", playerSubCmd)
```
## Command Implementation
### Player Commands
Located in `player.go`, these include:
- **Communication**: `/say`, `/tell`, `/yell`, `/shout`, `/ooc`, `/emote`
- **Group Management**: `/group`, `/groupsay`, `/gsay`
- **Guild Management**: `/guild`, `/guildsay`, `/officersay`
- **Information**: `/who`, `/time`, `/location`, `/consider`
- **Character**: `/afk`, `/anon`, `/lfg`, `/inventory`
- **Utilities**: `/help`, `/quit`, `/trade`, `/quest`
### Admin Commands
Located in `admin.go`, these include:
- **Player Management**: `/kick`, `/ban`, `/unban`, `/summon`, `/goto`
- **Communication**: `/broadcast`, `/announce`
- **Spawn Management**: `/spawn`, `/npc`
- **Item Management**: `/item`, `/giveitem`
- **Character Modification**: `/modify`, `/setlevel`, `/setclass`
- **Server Management**: `/reload`, `/shutdown`, `/cancelshutdown`
- **GM Utilities**: `/invisible`, `/invulnerable`, `/speed`, `/flymode`
- **Information**: `/info`, `/version`
### Console Commands
Located in `console.go`, these include prefixed versions of admin commands for console use:
- **Player Management**: `console_ban`, `console_kick`, `console_unban`
- **Communication**: `console_broadcast`, `console_announce`, `console_tell`
- **Information**: `console_guild`, `player`, `console_zone`, `console_who`
- **Server Management**: `console_reload`, `console_shutdown`, `exit`
- **MOTD Management**: `getmotd`, `setmotd`
- **Rules Management**: `rules`
## Message Channels and Colors
Commands can send messages through various channels:
### Channels
- `ChannelSay` (8): Local say messages
- `ChannelTell` (28): Private messages
- `ChannelBroadcast` (92): Server-wide broadcasts
- `ChannelError` (3): Error messages
- `ChannelStatus` (4): Status updates
### Colors
- `ColorWhite` (254): Standard text
- `ColorRed` (3): Errors and warnings
- `ColorYellow` (5): Status messages
- `ColorChatRelation` (4): Relationship text
## Error Handling
The command system provides comprehensive error handling:
```go
func HandleExampleCommand(ctx *CommandContext) error {
// Validate player is present
if err := ctx.RequirePlayer(); err != nil {
return err
}
// Validate argument count
if err := ctx.ValidateArgumentCount(1, 3); err != nil {
ctx.AddErrorMessage("Usage: /example <required> [optional1] [optional2]")
return nil
}
// Validate admin level
if err := ctx.RequireAdminLevel(AdminLevelGM); err != nil {
return err
}
// Command logic here...
return nil
}
```
## Testing
The command system includes comprehensive tests in `commands_test.go`:
```bash
# Run all command tests
go test ./internal/commands -v
# Run specific test
go test ./internal/commands -run TestCommandManager_Execute
```
Tests cover:
- Command registration and retrieval
- Argument parsing and validation
- Message handling
- Admin level checking
- Subcommand functionality
- Error conditions
## Integration with Server
To integrate the command system with the server:
```go
// Initialize commands during server startup
commandManager, err := commands.InitializeCommands()
if err != nil {
log.Fatalf("Failed to initialize commands: %v", err)
}
// Handle incoming command from client
func handleClientCommand(client ClientInterface, rawCommand string) {
err := commands.ExecuteCommand(commandManager, rawCommand, client)
if err != nil {
log.Printf("Command execution error: %v", err)
}
}
```
## Thread Safety
The command system is designed to be thread-safe:
- CommandManager uses read/write mutexes for concurrent access
- CommandContext uses mutexes for message and result storage
- All operations are safe for concurrent use
## Extension Points
The system is designed for easy extension:
### Custom Command Types
```go
const CommandTypeCustom CommandType = 100
func (ct CommandType) String() string {
switch ct {
case CommandTypeCustom:
return "custom"
default:
return CommandType(ct).String()
}
}
```
### Custom Handlers
```go
func MyCustomHandler(ctx *CommandContext) error {
// Custom command logic
ctx.AddStatusMessage("Custom command executed!")
return nil
}
```
## Performance Considerations
- Commands are indexed by name in a hash map for O(1) lookup
- Case-insensitive matching uses normalized lowercase keys
- Message batching reduces client communication overhead
- Argument parsing is lazy and type-safe
## Migration from C++
Key differences from the original C++ implementation:
1. **Type Safety**: Go's type system prevents many runtime errors
2. **Memory Management**: Automatic garbage collection eliminates memory leaks
3. **Concurrency**: Native goroutine support for concurrent operations
4. **Error Handling**: Explicit error returns instead of exceptions
5. **Testing**: Built-in testing framework with comprehensive coverage
## Future Enhancements
Planned improvements:
- Lua script integration for dynamic commands
- Command aliasing system
- Advanced permission system
- Command cooldowns and rate limiting
- Audit logging for admin commands
- Dynamic command loading/unloading
## Troubleshooting
### Common Issues
**Command Not Found**: Ensure command is registered and name matches exactly
**Insufficient Privileges**: Check admin level requirements
**Argument Validation**: Use proper argument count validation
**Interface Errors**: Ensure client implements ClientInterface correctly
### Debug Output
Enable debug output for command execution:
```go
ctx.AddDefaultMessage(fmt.Sprintf("Debug: Command %s executed with %d args",
ctx.CommandName, ctx.ArgumentCount()))
```
## Conclusion
The EQ2Go command system provides a robust, extensible framework for handling all types of game commands while maintaining compatibility with the original EverQuest II protocol. The system's modular design and comprehensive testing ensure reliability and ease of maintenance.

937
internal/commands/admin.go Normal file
View File

@ -0,0 +1,937 @@
package commands
import (
"fmt"
"strings"
)
// RegisterAdminCommands registers all admin-level commands
func RegisterAdminCommands(cm *CommandManager) error {
commands := []*Command{
// Player management commands
{
Name: "kick",
Type: CommandTypeAdmin,
Description: "Kicks a player from the server",
Usage: "/kick <player> [reason]",
RequiredLevel: AdminLevelGM,
Handler: HandleKick,
},
{
Name: "ban",
Type: CommandTypeAdmin,
Description: "Bans a player from the server",
Usage: "/ban <player> [duration] [reason]",
RequiredLevel: AdminLevelAdmin,
Handler: HandleBan,
},
{
Name: "unban",
Type: CommandTypeAdmin,
Description: "Unbans a player",
Usage: "/unban <player>",
RequiredLevel: AdminLevelAdmin,
Handler: HandleUnban,
},
{
Name: "summon",
Type: CommandTypeAdmin,
Description: "Summons a player to your location",
Usage: "/summon <player>",
RequiredLevel: AdminLevelGM,
Handler: HandleSummon,
},
{
Name: "goto",
Type: CommandTypeAdmin,
Description: "Teleports you to a player or location",
Usage: "/goto <player|x y z>",
RequiredLevel: AdminLevelGM,
Handler: HandleGoto,
},
{
Name: "zone",
Type: CommandTypeAdmin,
Description: "Zone management commands",
Usage: "/zone <player> <zone_name>",
RequiredLevel: AdminLevelGM,
Handler: HandleZonePlayer,
},
// Communication commands
{
Name: "broadcast",
Type: CommandTypeAdmin,
Description: "Broadcasts a message to all players",
Usage: "/broadcast <message>",
RequiredLevel: AdminLevelGM,
Handler: HandleBroadcast,
},
{
Name: "announce",
Type: CommandTypeAdmin,
Description: "Announces a message to all players",
Usage: "/announce <message>",
RequiredLevel: AdminLevelGM,
Handler: HandleAnnounce,
},
// Spawn management commands
{
Name: "spawn",
Type: CommandTypeAdmin,
Description: "Spawn management commands",
Usage: "/spawn <create|remove|list|set> [options]",
RequiredLevel: AdminLevelGM,
Handler: HandleSpawn,
},
{
Name: "npc",
Type: CommandTypeAdmin,
Description: "NPC management commands",
Usage: "/npc <create|remove|edit> [options]",
RequiredLevel: AdminLevelGM,
Handler: HandleNPC,
},
// Item commands
{
Name: "item",
Type: CommandTypeAdmin,
Description: "Item management commands",
Usage: "/item <give|remove|create> [options]",
RequiredLevel: AdminLevelGM,
Handler: HandleItem,
},
{
Name: "giveitem",
Type: CommandTypeAdmin,
Description: "Gives an item to a player",
Usage: "/giveitem <player> <item_id> [quantity]",
RequiredLevel: AdminLevelGM,
Handler: HandleGiveItem,
},
// Character modification commands
{
Name: "modify",
Type: CommandTypeAdmin,
Description: "Modification commands",
Usage: "/modify <character|spawn|zone|quest|item> [options]",
RequiredLevel: AdminLevelAdmin,
Handler: HandleModify,
},
{
Name: "setlevel",
Type: CommandTypeAdmin,
Description: "Sets a player's level",
Usage: "/setlevel <player> <level>",
RequiredLevel: AdminLevelGM,
Handler: HandleSetLevel,
},
{
Name: "setclass",
Type: CommandTypeAdmin,
Description: "Sets a player's class",
Usage: "/setclass <player> <class_id>",
RequiredLevel: AdminLevelAdmin,
Handler: HandleSetClass,
},
// Server management commands
{
Name: "reload",
Type: CommandTypeAdmin,
Description: "Reloads server data",
Usage: "/reload <spells|quests|zones|items|all>",
RequiredLevel: AdminLevelAdmin,
Handler: HandleReload,
},
{
Name: "shutdown",
Type: CommandTypeAdmin,
Description: "Shuts down the server",
Usage: "/shutdown [time_minutes] [reason]",
RequiredLevel: AdminLevelAdmin,
Handler: HandleShutdown,
},
{
Name: "cancelshutdown",
Type: CommandTypeAdmin,
Description: "Cancels a scheduled shutdown",
Usage: "/cancelshutdown",
RequiredLevel: AdminLevelAdmin,
Handler: HandleCancelShutdown,
},
// GM utility commands
{
Name: "invisible",
Type: CommandTypeAdmin,
Description: "Toggles GM invisibility",
Usage: "/invisible",
RequiredLevel: AdminLevelGM,
Handler: HandleInvisible,
},
{
Name: "invulnerable",
Type: CommandTypeAdmin,
Description: "Toggles invulnerability",
Usage: "/invulnerable",
RequiredLevel: AdminLevelGM,
Handler: HandleInvulnerable,
},
{
Name: "speed",
Type: CommandTypeAdmin,
Description: "Sets movement speed",
Usage: "/speed <multiplier>",
RequiredLevel: AdminLevelGM,
Handler: HandleSpeed,
},
{
Name: "flymode",
Type: CommandTypeAdmin,
Description: "Toggles fly mode",
Usage: "/flymode",
RequiredLevel: AdminLevelGM,
Handler: HandleFlyMode,
},
// Information commands
{
Name: "info",
Type: CommandTypeAdmin,
Description: "Shows detailed information about target",
Usage: "/info [target]",
RequiredLevel: AdminLevelGM,
Handler: HandleInfo,
},
{
Name: "version",
Type: CommandTypeAdmin,
Description: "Shows server version information",
Usage: "/version",
RequiredLevel: AdminLevelGM,
Handler: HandleVersion,
},
// Testing commands
{
Name: "test",
Type: CommandTypeAdmin,
Description: "Testing command for development",
Usage: "/test [parameters]",
RequiredLevel: AdminLevelAdmin,
Handler: HandleTest,
},
}
for _, cmd := range commands {
if err := cm.Register(cmd); err != nil {
return fmt.Errorf("failed to register admin command %s: %w", cmd.Name, err)
}
}
// Register subcommands
if err := registerModifySubcommands(cm); err != nil {
return fmt.Errorf("failed to register modify subcommands: %w", err)
}
if err := registerSpawnSubcommands(cm); err != nil {
return fmt.Errorf("failed to register spawn subcommands: %w", err)
}
return nil
}
// registerModifySubcommands registers subcommands for the /modify command
func registerModifySubcommands(cm *CommandManager) error {
subcommands := []*Command{
{
Name: "character",
Type: CommandTypeAdmin,
Description: "Modifies character attributes",
Usage: "/modify character <player> <attribute> <value>",
RequiredLevel: AdminLevelAdmin,
Handler: HandleModifyCharacter,
},
{
Name: "spawn",
Type: CommandTypeAdmin,
Description: "Modifies spawn attributes",
Usage: "/modify spawn <spawn_id> <attribute> <value>",
RequiredLevel: AdminLevelAdmin,
Handler: HandleModifySpawn,
},
{
Name: "zone",
Type: CommandTypeAdmin,
Description: "Modifies zone attributes",
Usage: "/modify zone <zone_id> <attribute> <value>",
RequiredLevel: AdminLevelAdmin,
Handler: HandleModifyZone,
},
}
for _, subcmd := range subcommands {
if err := cm.RegisterSubCommand("modify", subcmd); err != nil {
return err
}
}
return nil
}
// registerSpawnSubcommands registers subcommands for the /spawn command
func registerSpawnSubcommands(cm *CommandManager) error {
subcommands := []*Command{
{
Name: "create",
Type: CommandTypeAdmin,
Description: "Creates a new spawn",
Usage: "/spawn create <type> [options]",
RequiredLevel: AdminLevelGM,
Handler: HandleSpawnCreate,
},
{
Name: "remove",
Type: CommandTypeAdmin,
Description: "Removes a spawn",
Usage: "/spawn remove <spawn_id>",
RequiredLevel: AdminLevelGM,
Handler: HandleSpawnRemove,
},
{
Name: "list",
Type: CommandTypeAdmin,
Description: "Lists spawns in area",
Usage: "/spawn list [radius]",
RequiredLevel: AdminLevelGM,
Handler: HandleSpawnList,
},
}
for _, subcmd := range subcommands {
if err := cm.RegisterSubCommand("spawn", subcmd); err != nil {
return err
}
}
return nil
}
// HandleKick handles the /kick command
func HandleKick(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: /kick <player> [reason]")
return nil
}
playerName := ctx.Arguments[0]
reason := "Kicked by administrator"
if ctx.ArgumentCount() > 1 {
reason = ctx.GetRemainingArguments(1)
}
// TODO: Implement actual kick functionality
ctx.AddStatusMessage(fmt.Sprintf("Kicked player '%s' with reason: %s", playerName, reason))
// TODO: Find and disconnect player
// if player := world.FindPlayerByName(playerName); player != nil {
// player.Disconnect(reason)
// broadcast to admins about kick
// } else {
// ctx.AddErrorMessage(fmt.Sprintf("Player '%s' not found", playerName))
// }
return nil
}
// HandleBan handles the /ban command
func HandleBan(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: /ban <player> [duration] [reason]")
return nil
}
playerName := ctx.Arguments[0]
duration := "permanent"
reason := "Banned by administrator"
if ctx.ArgumentCount() > 1 {
duration = ctx.Arguments[1]
}
if ctx.ArgumentCount() > 2 {
reason = ctx.GetRemainingArguments(2)
}
// TODO: Implement actual ban functionality
ctx.AddStatusMessage(fmt.Sprintf("Banned player '%s' for %s with reason: %s",
playerName, duration, reason))
return nil
}
// HandleUnban handles the /unban command
func HandleUnban(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(1, 1); err != nil {
ctx.AddErrorMessage("Usage: /unban <player>")
return nil
}
playerName := ctx.Arguments[0]
// TODO: Implement actual unban functionality
ctx.AddStatusMessage(fmt.Sprintf("Unbanned player '%s'", playerName))
return nil
}
// HandleSummon handles the /summon command
func HandleSummon(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if err := ctx.ValidateArgumentCount(1, 1); err != nil {
ctx.AddErrorMessage("Usage: /summon <player>")
return nil
}
targetName := ctx.Arguments[0]
// TODO: Implement actual summon functionality
ctx.AddStatusMessage(fmt.Sprintf("Summoned player '%s' to your location", targetName))
// TODO: Find target player and teleport to admin location
// if target := world.FindPlayerByName(targetName); target != nil {
// adminPos := ctx.Player.GetPosition()
// target.Teleport(adminPos.X, adminPos.Y, adminPos.Z, ctx.Zone.GetID())
// } else {
// ctx.AddErrorMessage(fmt.Sprintf("Player '%s' not found", targetName))
// }
return nil
}
// HandleGoto handles the /goto command
func HandleGoto(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if err := ctx.ValidateArgumentCount(1, 3); err != nil {
ctx.AddErrorMessage("Usage: /goto <player> or /goto <x> <y> <z>")
return nil
}
if ctx.ArgumentCount() == 1 {
// Go to player
targetName := ctx.Arguments[0]
// TODO: Implement teleport to player
ctx.AddStatusMessage(fmt.Sprintf("Teleported to player '%s'", targetName))
} else if ctx.ArgumentCount() == 3 {
// Go to coordinates
x := ctx.GetArgumentFloat(0, 0)
y := ctx.GetArgumentFloat(1, 0)
z := ctx.GetArgumentFloat(2, 0)
// TODO: Implement teleport to coordinates
ctx.AddStatusMessage(fmt.Sprintf("Teleported to coordinates (%.2f, %.2f, %.2f)", x, y, z))
} else {
ctx.AddErrorMessage("Usage: /goto <player> or /goto <x> <y> <z>")
}
return nil
}
// HandleZonePlayer handles the /zone command
func HandleZonePlayer(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(2, 2); err != nil {
ctx.AddErrorMessage("Usage: /zone <player> <zone_name>")
return nil
}
playerName := ctx.Arguments[0]
zoneName := ctx.Arguments[1]
// TODO: Implement zone transfer
ctx.AddStatusMessage(fmt.Sprintf("Zoned player '%s' to '%s'", playerName, zoneName))
return nil
}
// HandleBroadcast handles the /broadcast command
func HandleBroadcast(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: /broadcast <message>")
return nil
}
message := ctx.RawArguments
adminName := ctx.GetPlayerName()
// TODO: Implement server-wide broadcast
ctx.AddMessage(ChannelBroadcast, ColorYellow,
fmt.Sprintf("[BROADCAST] %s: %s", adminName, message))
return nil
}
// HandleAnnounce handles the /announce command
func HandleAnnounce(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: /announce <message>")
return nil
}
message := ctx.RawArguments
// TODO: Implement server announcement
ctx.AddMessage(ChannelBroadcast, ColorRed, fmt.Sprintf("[ANNOUNCEMENT] %s", message))
return nil
}
// HandleSpawn handles the main /spawn command
func HandleSpawn(ctx *CommandContext) error {
if ctx.ArgumentCount() == 0 {
ctx.AddErrorMessage("Usage: /spawn <create|remove|list|set> [options]")
ctx.AddErrorMessage("Use /spawn <subcommand> for specific help")
return nil
}
// Subcommands are handled by the command manager
return fmt.Errorf("spawn subcommand '%s' not found", ctx.Arguments[0])
}
// HandleSpawnCreate handles the /spawn create subcommand
func HandleSpawnCreate(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: /spawn create <type> [name] [level]")
return nil
}
spawnType := ctx.Arguments[0]
spawnName := "New Spawn"
spawnLevel := 1
if ctx.ArgumentCount() > 1 {
spawnName = ctx.Arguments[1]
}
if ctx.ArgumentCount() > 2 {
spawnLevel = ctx.GetArgumentInt(2, 1)
}
// TODO: Implement spawn creation
ctx.AddStatusMessage(fmt.Sprintf("Created %s spawn '%s' (level %d) at your location",
spawnType, spawnName, spawnLevel))
return nil
}
// HandleSpawnRemove handles the /spawn remove subcommand
func HandleSpawnRemove(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(1, 1); err != nil {
ctx.AddErrorMessage("Usage: /spawn remove <spawn_id>")
return nil
}
spawnID := ctx.GetArgumentInt(0, 0)
if spawnID <= 0 {
ctx.AddErrorMessage("Invalid spawn ID")
return nil
}
// TODO: Implement spawn removal
ctx.AddStatusMessage(fmt.Sprintf("Removed spawn with ID %d", spawnID))
return nil
}
// HandleSpawnList handles the /spawn list subcommand
func HandleSpawnList(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
radius := 50.0 // Default radius
if ctx.ArgumentCount() > 0 {
radius = ctx.GetArgumentFloat(0, 50.0)
}
// TODO: Implement spawn listing
ctx.AddStatusMessage(fmt.Sprintf("Spawns within %.1f units:", radius))
ctx.AddStatusMessage(" ID: 1, Name: Test NPC, Level: 10, Distance: 5.2")
ctx.AddStatusMessage("Total: 1 spawn found")
return nil
}
// HandleNPC handles the /npc command
func HandleNPC(ctx *CommandContext) error {
if ctx.ArgumentCount() == 0 {
ctx.AddErrorMessage("Usage: /npc <create|remove|edit> [options]")
return nil
}
subCommand := strings.ToLower(ctx.Arguments[0])
switch subCommand {
case "create":
// TODO: Implement NPC creation
ctx.AddStatusMessage("NPC creation (not yet implemented)")
case "remove":
// TODO: Implement NPC removal
ctx.AddStatusMessage("NPC removal (not yet implemented)")
case "edit":
// TODO: Implement NPC editing
ctx.AddStatusMessage("NPC editing (not yet implemented)")
default:
ctx.AddErrorMessage("Usage: /npc <create|remove|edit> [options]")
}
return nil
}
// HandleItem handles the /item command
func HandleItem(ctx *CommandContext) error {
if ctx.ArgumentCount() == 0 {
ctx.AddErrorMessage("Usage: /item <give|remove|create> [options]")
return nil
}
subCommand := strings.ToLower(ctx.Arguments[0])
switch subCommand {
case "give":
if ctx.ArgumentCount() < 3 {
ctx.AddErrorMessage("Usage: /item give <player> <item_id> [quantity]")
return nil
}
return handleItemGive(ctx)
case "remove":
// TODO: Implement item removal
ctx.AddStatusMessage("Item removal (not yet implemented)")
case "create":
// TODO: Implement item creation
ctx.AddStatusMessage("Item creation (not yet implemented)")
default:
ctx.AddErrorMessage("Usage: /item <give|remove|create> [options]")
}
return nil
}
// handleItemGive handles giving items to players
func handleItemGive(ctx *CommandContext) error {
playerName := ctx.Arguments[1]
itemID := ctx.GetArgumentInt(2, 0)
quantity := ctx.GetArgumentInt(3, 1)
if itemID <= 0 {
ctx.AddErrorMessage("Invalid item ID")
return nil
}
// TODO: Implement item giving
ctx.AddStatusMessage(fmt.Sprintf("Gave %d x item %d to player '%s'",
quantity, itemID, playerName))
return nil
}
// HandleGiveItem handles the /giveitem command
func HandleGiveItem(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(2, 3); err != nil {
ctx.AddErrorMessage("Usage: /giveitem <player> <item_id> [quantity]")
return nil
}
playerName := ctx.Arguments[0]
itemID := ctx.GetArgumentInt(1, 0)
quantity := ctx.GetArgumentInt(2, 1)
if itemID <= 0 {
ctx.AddErrorMessage("Invalid item ID")
return nil
}
// TODO: Implement item giving
ctx.AddStatusMessage(fmt.Sprintf("Gave %d x item %d to player '%s'",
quantity, itemID, playerName))
return nil
}
// HandleModify handles the main /modify command
func HandleModify(ctx *CommandContext) error {
if ctx.ArgumentCount() == 0 {
ctx.AddErrorMessage("Usage: /modify <character|spawn|zone|quest|item> [options]")
return nil
}
// Subcommands are handled by the command manager
return fmt.Errorf("modify subcommand '%s' not found", ctx.Arguments[0])
}
// HandleModifyCharacter handles the /modify character subcommand
func HandleModifyCharacter(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(3, 3); err != nil {
ctx.AddErrorMessage("Usage: /modify character <player> <attribute> <value>")
return nil
}
playerName := ctx.Arguments[0]
attribute := ctx.Arguments[1]
value := ctx.Arguments[2]
// TODO: Implement character modification
ctx.AddStatusMessage(fmt.Sprintf("Modified %s's %s to %s", playerName, attribute, value))
return nil
}
// HandleModifySpawn handles the /modify spawn subcommand
func HandleModifySpawn(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(3, 3); err != nil {
ctx.AddErrorMessage("Usage: /modify spawn <spawn_id> <attribute> <value>")
return nil
}
spawnID := ctx.GetArgumentInt(0, 0)
attribute := ctx.Arguments[1]
value := ctx.Arguments[2]
if spawnID <= 0 {
ctx.AddErrorMessage("Invalid spawn ID")
return nil
}
// TODO: Implement spawn modification
ctx.AddStatusMessage(fmt.Sprintf("Modified spawn %d's %s to %s", spawnID, attribute, value))
return nil
}
// HandleModifyZone handles the /modify zone subcommand
func HandleModifyZone(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(3, 3); err != nil {
ctx.AddErrorMessage("Usage: /modify zone <zone_id> <attribute> <value>")
return nil
}
zoneID := ctx.GetArgumentInt(0, 0)
attribute := ctx.Arguments[1]
value := ctx.Arguments[2]
if zoneID <= 0 {
ctx.AddErrorMessage("Invalid zone ID")
return nil
}
// TODO: Implement zone modification
ctx.AddStatusMessage(fmt.Sprintf("Modified zone %d's %s to %s", zoneID, attribute, value))
return nil
}
// HandleSetLevel handles the /setlevel command
func HandleSetLevel(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(2, 2); err != nil {
ctx.AddErrorMessage("Usage: /setlevel <player> <level>")
return nil
}
playerName := ctx.Arguments[0]
level := ctx.GetArgumentInt(1, 1)
if level < 1 || level > 100 {
ctx.AddErrorMessage("Level must be between 1 and 100")
return nil
}
// TODO: Implement level setting
ctx.AddStatusMessage(fmt.Sprintf("Set %s's level to %d", playerName, level))
return nil
}
// HandleSetClass handles the /setclass command
func HandleSetClass(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(2, 2); err != nil {
ctx.AddErrorMessage("Usage: /setclass <player> <class_id>")
return nil
}
playerName := ctx.Arguments[0]
classID := ctx.GetArgumentInt(1, 0)
if classID < 0 {
ctx.AddErrorMessage("Invalid class ID")
return nil
}
// TODO: Implement class setting
ctx.AddStatusMessage(fmt.Sprintf("Set %s's class to %d", playerName, classID))
return nil
}
// HandleReload handles the /reload command
func HandleReload(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(1, 1); err != nil {
ctx.AddErrorMessage("Usage: /reload <spells|quests|zones|items|all>")
return nil
}
reloadType := strings.ToLower(ctx.Arguments[0])
switch reloadType {
case "spells":
ctx.AddStatusMessage("Reloaded spells")
case "quests":
ctx.AddStatusMessage("Reloaded quests")
case "zones":
ctx.AddStatusMessage("Reloaded zones")
case "items":
ctx.AddStatusMessage("Reloaded items")
case "all":
ctx.AddStatusMessage("Reloaded all server data")
default:
ctx.AddErrorMessage("Usage: /reload <spells|quests|zones|items|all>")
}
return nil
}
// HandleShutdown handles the /shutdown command
func HandleShutdown(ctx *CommandContext) error {
minutes := 5 // Default 5 minutes
reason := "Server maintenance"
if ctx.ArgumentCount() > 0 {
minutes = ctx.GetArgumentInt(0, 5)
}
if ctx.ArgumentCount() > 1 {
reason = ctx.GetRemainingArguments(1)
}
// TODO: Implement server shutdown
ctx.AddStatusMessage(fmt.Sprintf("Server shutdown scheduled in %d minutes. Reason: %s",
minutes, reason))
return nil
}
// HandleCancelShutdown handles the /cancelshutdown command
func HandleCancelShutdown(ctx *CommandContext) error {
// TODO: Implement shutdown cancellation
ctx.AddStatusMessage("Server shutdown cancelled")
return nil
}
// HandleInvisible handles the /invisible command
func HandleInvisible(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
// TODO: Toggle GM invisibility
ctx.AddStatusMessage("GM invisibility toggled")
return nil
}
// HandleInvulnerable handles the /invulnerable command
func HandleInvulnerable(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
// TODO: Toggle invulnerability
ctx.AddStatusMessage("Invulnerability toggled")
return nil
}
// HandleSpeed handles the /speed command
func HandleSpeed(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if err := ctx.ValidateArgumentCount(1, 1); err != nil {
ctx.AddErrorMessage("Usage: /speed <multiplier>")
return nil
}
multiplier := ctx.GetArgumentFloat(0, 1.0)
if multiplier < 0.1 || multiplier > 10.0 {
ctx.AddErrorMessage("Speed multiplier must be between 0.1 and 10.0")
return nil
}
// TODO: Set movement speed
ctx.AddStatusMessage(fmt.Sprintf("Movement speed set to %.1fx", multiplier))
return nil
}
// HandleFlyMode handles the /flymode command
func HandleFlyMode(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
// TODO: Toggle fly mode
ctx.AddStatusMessage("Fly mode toggled")
return nil
}
// HandleInfo handles the /info command
func HandleInfo(ctx *CommandContext) error {
// TODO: Show detailed target information
targetName := ctx.GetTargetName()
if targetName == "No Target" {
ctx.AddErrorMessage("You must target something to get info")
return nil
}
ctx.AddStatusMessage(fmt.Sprintf("Information for %s:", targetName))
ctx.AddStatusMessage(" ID: 12345")
ctx.AddStatusMessage(" Level: 25")
ctx.AddStatusMessage(" HP: 1250/1250")
ctx.AddStatusMessage(" Position: 100.5, 200.3, 150.7")
return nil
}
// HandleVersion handles the /version command
func HandleVersion(ctx *CommandContext) error {
// TODO: Get actual version information
ctx.AddStatusMessage("EQ2Go Server Version 1.0.0")
ctx.AddStatusMessage("Build: Development")
ctx.AddStatusMessage("Go Version: go1.24.5")
return nil
}
// HandleTest handles the /test command
func HandleTest(ctx *CommandContext) error {
ctx.AddStatusMessage("Test command executed successfully")
if ctx.ArgumentCount() > 0 {
ctx.AddStatusMessage(fmt.Sprintf("Arguments: %s", ctx.RawArguments))
}
// TODO: Add actual test functionality for development
return nil
}

View File

@ -0,0 +1,85 @@
package commands
import "fmt"
// InitializeCommands initializes and registers all command types
func InitializeCommands() (*CommandManager, error) {
cm := NewCommandManager()
// Register all command types
if err := RegisterPlayerCommands(cm); err != nil {
return nil, fmt.Errorf("failed to register player commands: %w", err)
}
if err := RegisterAdminCommands(cm); err != nil {
return nil, fmt.Errorf("failed to register admin commands: %w", err)
}
if err := RegisterConsoleCommands(cm); err != nil {
return nil, fmt.Errorf("failed to register console commands: %w", err)
}
return cm, nil
}
// GetCommandHelp returns help text for a specific command
func GetCommandHelp(cm *CommandManager, commandName string, adminLevel int) string {
command, exists := cm.GetCommand(commandName)
if !exists {
return fmt.Sprintf("Command '%s' not found", commandName)
}
if adminLevel < command.RequiredLevel {
return fmt.Sprintf("Command '%s' not available at your admin level", commandName)
}
help := fmt.Sprintf("Command: %s\n", command.Name)
help += fmt.Sprintf("Usage: %s\n", command.Usage)
help += fmt.Sprintf("Description: %s\n", command.Description)
help += fmt.Sprintf("Required Admin Level: %d\n", command.RequiredLevel)
help += fmt.Sprintf("Type: %s\n", command.Type.String())
if len(command.SubCommands) > 0 {
help += "\nSubcommands:\n"
for name, subCmd := range command.SubCommands {
if adminLevel >= subCmd.RequiredLevel {
help += fmt.Sprintf(" %s - %s\n", name, subCmd.Description)
}
}
}
return help
}
// GetAvailableCommands returns a list of commands available to the given admin level
func GetAvailableCommands(cm *CommandManager, adminLevel int) []string {
allCommands := cm.ListCommands()
available := make([]string, 0)
for _, cmdName := range allCommands {
if command, exists := cm.GetCommand(cmdName); exists {
if adminLevel >= command.RequiredLevel {
available = append(available, cmdName)
}
}
}
return available
}
// ExecuteCommand is a convenience function that parses and executes a command
func ExecuteCommand(cm *CommandManager, rawCommand string, client ClientInterface) error {
ctx := cm.ParseCommand(rawCommand, client)
if ctx == nil {
return fmt.Errorf("failed to parse command")
}
err := cm.Execute(ctx)
if err != nil {
ctx.AddErrorMessage(err.Error())
}
// Send all messages to client
ctx.SendMessages()
return err
}

View File

@ -0,0 +1,614 @@
package commands
import (
"testing"
"eq2emu/internal/entity"
"eq2emu/internal/spawn"
)
// Mock implementations for testing
type mockClient struct {
player *entity.Entity
accountID int32
characterID int32
adminLevel int
name string
zone *mockZone
messages []mockMessage
}
type mockMessage struct {
channel int
color int
message string
}
func (mc *mockClient) GetPlayer() *entity.Entity { return mc.player }
func (mc *mockClient) GetAccountID() int32 { return mc.accountID }
func (mc *mockClient) GetCharacterID() int32 { return mc.characterID }
func (mc *mockClient) GetAdminLevel() int { return mc.adminLevel }
func (mc *mockClient) GetName() string { return mc.name }
func (mc *mockClient) IsInZone() bool { return mc.zone != nil }
func (mc *mockClient) GetZone() ZoneInterface { return mc.zone }
func (mc *mockClient) SendMessage(channel, color int, message string) {
mc.messages = append(mc.messages, mockMessage{channel, color, message})
}
func (mc *mockClient) SendPopupMessage(message string) {}
func (mc *mockClient) Disconnect() {}
type mockZone struct {
id int32
name string
description string
players []*entity.Entity
}
func (mz *mockZone) GetID() int32 { return mz.id }
func (mz *mockZone) GetName() string { return mz.name }
func (mz *mockZone) GetDescription() string { return mz.description }
func (mz *mockZone) GetPlayers() []*entity.Entity { return mz.players }
func (mz *mockZone) Shutdown() {}
func (mz *mockZone) SendZoneMessage(channel, color int, message string) {}
func (mz *mockZone) GetSpawnByName(name string) *spawn.Spawn { return nil }
func (mz *mockZone) GetSpawnByID(id int32) *spawn.Spawn { return nil }
func TestCommandManager_Register(t *testing.T) {
cm := NewCommandManager()
// Test registering a valid command
cmd := &Command{
Name: "test",
Type: CommandTypePlayer,
Description: "Test command",
Usage: "/test",
RequiredLevel: AdminLevelPlayer,
Handler: func(ctx *CommandContext) error {
ctx.AddStatusMessage("Test executed")
return nil
},
}
err := cm.Register(cmd)
if err != nil {
t.Errorf("Failed to register valid command: %v", err)
}
// Test registering duplicate command
err = cm.Register(cmd)
if err == nil {
t.Error("Expected error when registering duplicate command")
}
// Test registering nil command
err = cm.Register(nil)
if err == nil {
t.Error("Expected error when registering nil command")
}
// Test registering command with empty name
invalidCmd := &Command{
Name: "",
Handler: func(ctx *CommandContext) error { return nil },
}
err = cm.Register(invalidCmd)
if err == nil {
t.Error("Expected error when registering command with empty name")
}
// Test registering command with nil handler
invalidCmd2 := &Command{
Name: "invalid",
Handler: nil,
}
err = cm.Register(invalidCmd2)
if err == nil {
t.Error("Expected error when registering command with nil handler")
}
}
func TestCommandManager_HasCommand(t *testing.T) {
cm := NewCommandManager()
cmd := &Command{
Name: "testcmd",
Type: CommandTypePlayer,
RequiredLevel: AdminLevelPlayer,
Handler: func(ctx *CommandContext) error { return nil },
}
// Test command doesn't exist initially
if cm.HasCommand("testcmd") {
t.Error("Command should not exist initially")
}
// Register command
err := cm.Register(cmd)
if err != nil {
t.Fatalf("Failed to register command: %v", err)
}
// Test command exists after registration
if !cm.HasCommand("testcmd") {
t.Error("Command should exist after registration")
}
// Test case insensitivity
if !cm.HasCommand("TESTCMD") {
t.Error("Command lookup should be case insensitive")
}
}
func TestCommandManager_GetCommand(t *testing.T) {
cm := NewCommandManager()
cmd := &Command{
Name: "getcmd",
Type: CommandTypeAdmin,
RequiredLevel: AdminLevelGM,
Handler: func(ctx *CommandContext) error { return nil },
}
// Test getting non-existent command
_, exists := cm.GetCommand("getcmd")
if exists {
t.Error("Command should not exist initially")
}
// Register command
err := cm.Register(cmd)
if err != nil {
t.Fatalf("Failed to register command: %v", err)
}
// Test getting existing command
retrievedCmd, exists := cm.GetCommand("getcmd")
if !exists {
t.Error("Command should exist after registration")
}
if retrievedCmd.Name != "getcmd" {
t.Errorf("Expected command name 'getcmd', got '%s'", retrievedCmd.Name)
}
if retrievedCmd.Type != CommandTypeAdmin {
t.Errorf("Expected command type Admin, got %v", retrievedCmd.Type)
}
// Test case insensitivity
_, exists = cm.GetCommand("GETCMD")
if !exists {
t.Error("Command lookup should be case insensitive")
}
}
func TestCommandManager_Execute(t *testing.T) {
cm := NewCommandManager()
executed := false
cmd := &Command{
Name: "exectest",
Type: CommandTypePlayer,
RequiredLevel: AdminLevelPlayer,
Handler: func(ctx *CommandContext) error {
executed = true
ctx.AddStatusMessage("Command executed successfully")
return nil
},
}
err := cm.Register(cmd)
if err != nil {
t.Fatalf("Failed to register command: %v", err)
}
// Test successful execution
ctx := NewCommandContext(CommandTypePlayer, "exectest", []string{})
ctx.AdminLevel = AdminLevelPlayer
err = cm.Execute(ctx)
if err != nil {
t.Errorf("Command execution failed: %v", err)
}
if !executed {
t.Error("Command handler was not executed")
}
// Test insufficient admin level
ctx2 := NewCommandContext(CommandTypePlayer, "exectest", []string{})
ctx2.AdminLevel = 0
// Register admin-only command
adminCmd := &Command{
Name: "admintest",
Type: CommandTypeAdmin,
RequiredLevel: AdminLevelAdmin,
Handler: func(ctx *CommandContext) error {
return nil
},
}
err = cm.Register(adminCmd)
if err != nil {
t.Fatalf("Failed to register admin command: %v", err)
}
ctx3 := NewCommandContext(CommandTypeAdmin, "admintest", []string{})
ctx3.AdminLevel = AdminLevelPlayer
err = cm.Execute(ctx3)
if err == nil {
t.Error("Expected error for insufficient admin level")
}
// Test unknown command
ctx4 := NewCommandContext(CommandTypePlayer, "unknown", []string{})
err = cm.Execute(ctx4)
if err == nil {
t.Error("Expected error for unknown command")
}
}
func TestCommandManager_RegisterSubCommand(t *testing.T) {
cm := NewCommandManager()
// Register parent command
parentCmd := &Command{
Name: "parent",
Type: CommandTypeAdmin,
RequiredLevel: AdminLevelGM,
Handler: func(ctx *CommandContext) error {
return nil
},
}
err := cm.Register(parentCmd)
if err != nil {
t.Fatalf("Failed to register parent command: %v", err)
}
// Register subcommand
subCmd := &Command{
Name: "sub",
Type: CommandTypeAdmin,
RequiredLevel: AdminLevelGM,
Handler: func(ctx *CommandContext) error {
ctx.AddStatusMessage("Subcommand executed")
return nil
},
}
err = cm.RegisterSubCommand("parent", subCmd)
if err != nil {
t.Errorf("Failed to register subcommand: %v", err)
}
// Test registering subcommand for non-existent parent
err = cm.RegisterSubCommand("nonexistent", subCmd)
if err == nil {
t.Error("Expected error when registering subcommand for non-existent parent")
}
// Test registering duplicate subcommand
err = cm.RegisterSubCommand("parent", subCmd)
if err == nil {
t.Error("Expected error when registering duplicate subcommand")
}
}
func TestCommandManager_ParseCommand(t *testing.T) {
cm := NewCommandManager()
client := &mockClient{
name: "TestPlayer",
adminLevel: AdminLevelPlayer,
}
// Test parsing valid command
ctx := cm.ParseCommand("/say Hello world", client)
if ctx == nil {
t.Error("Expected non-nil context for valid command")
}
if ctx.CommandName != "say" {
t.Errorf("Expected command name 'say', got '%s'", ctx.CommandName)
}
if len(ctx.Arguments) != 2 {
t.Errorf("Expected 2 arguments, got %d", len(ctx.Arguments))
}
if ctx.Arguments[0] != "Hello" || ctx.Arguments[1] != "world" {
t.Errorf("Unexpected arguments: %v", ctx.Arguments)
}
if ctx.RawArguments != "Hello world" {
t.Errorf("Expected raw arguments 'Hello world', got '%s'", ctx.RawArguments)
}
// Test parsing command without slash
ctx2 := cm.ParseCommand("tell Player Hi there", client)
if ctx2 == nil {
t.Error("Expected non-nil context for command without slash")
}
if ctx2.CommandName != "tell" {
t.Errorf("Expected command name 'tell', got '%s'", ctx2.CommandName)
}
// Test parsing empty command
ctx3 := cm.ParseCommand("", client)
if ctx3 != nil {
t.Error("Expected nil context for empty command")
}
// Test parsing whitespace-only command
ctx4 := cm.ParseCommand(" ", client)
if ctx4 != nil {
t.Error("Expected nil context for whitespace-only command")
}
}
func TestCommandContext_ValidateArgumentCount(t *testing.T) {
ctx := NewCommandContext(CommandTypePlayer, "test", []string{"arg1", "arg2", "arg3"})
// Test valid argument count (exactly 3)
err := ctx.ValidateArgumentCount(3, 3)
if err != nil {
t.Errorf("Expected no error for exact argument count, got: %v", err)
}
// Test valid argument count (range 2-4)
err = ctx.ValidateArgumentCount(2, 4)
if err != nil {
t.Errorf("Expected no error for argument count in range, got: %v", err)
}
// Test too few arguments
err = ctx.ValidateArgumentCount(5, 6)
if err == nil {
t.Error("Expected error for too few arguments")
}
// Test too many arguments
err = ctx.ValidateArgumentCount(1, 2)
if err == nil {
t.Error("Expected error for too many arguments")
}
// Test unlimited max arguments (-1)
err = ctx.ValidateArgumentCount(2, -1)
if err != nil {
t.Errorf("Expected no error for unlimited max arguments, got: %v", err)
}
}
func TestCommandContext_GetArgument(t *testing.T) {
ctx := NewCommandContext(CommandTypePlayer, "test", []string{"first", "second", "third"})
// Test valid indices
arg, exists := ctx.GetArgument(0)
if !exists || arg != "first" {
t.Errorf("Expected 'first', got '%s' (exists: %v)", arg, exists)
}
arg, exists = ctx.GetArgument(2)
if !exists || arg != "third" {
t.Errorf("Expected 'third', got '%s' (exists: %v)", arg, exists)
}
// Test invalid indices
_, exists = ctx.GetArgument(-1)
if exists {
t.Error("Expected false for negative index")
}
_, exists = ctx.GetArgument(3)
if exists {
t.Error("Expected false for out-of-bounds index")
}
}
func TestCommandContext_GetArgumentInt(t *testing.T) {
ctx := NewCommandContext(CommandTypePlayer, "test", []string{"123", "not_a_number", "-456"})
// Test valid integer
val := ctx.GetArgumentInt(0, 999)
if val != 123 {
t.Errorf("Expected 123, got %d", val)
}
// Test invalid integer (should return default)
val = ctx.GetArgumentInt(1, 999)
if val != 999 {
t.Errorf("Expected default value 999, got %d", val)
}
// Test negative integer
val = ctx.GetArgumentInt(2, 999)
if val != -456 {
t.Errorf("Expected -456, got %d", val)
}
// Test out-of-bounds index (should return default)
val = ctx.GetArgumentInt(5, 999)
if val != 999 {
t.Errorf("Expected default value 999, got %d", val)
}
}
func TestCommandContext_GetArgumentFloat(t *testing.T) {
ctx := NewCommandContext(CommandTypePlayer, "test", []string{"12.34", "not_a_number", "-56.78"})
// Test valid float
val := ctx.GetArgumentFloat(0, 999.0)
if val != 12.34 {
t.Errorf("Expected 12.34, got %f", val)
}
// Test invalid float (should return default)
val = ctx.GetArgumentFloat(1, 999.0)
if val != 999.0 {
t.Errorf("Expected default value 999.0, got %f", val)
}
// Test negative float
val = ctx.GetArgumentFloat(2, 999.0)
if val != -56.78 {
t.Errorf("Expected -56.78, got %f", val)
}
}
func TestCommandContext_GetArgumentBool(t *testing.T) {
ctx := NewCommandContext(CommandTypePlayer, "test",
[]string{"true", "false", "yes", "no", "on", "off", "1", "0", "invalid"})
tests := []struct {
index int
expected bool
}{
{0, true}, // "true"
{1, false}, // "false"
{2, true}, // "yes"
{3, false}, // "no"
{4, true}, // "on"
{5, false}, // "off"
{6, true}, // "1"
{7, false}, // "0"
}
for _, test := range tests {
val := ctx.GetArgumentBool(test.index, !test.expected) // Use opposite as default
if val != test.expected {
t.Errorf("Index %d: expected %v, got %v", test.index, test.expected, val)
}
}
// Test invalid boolean (should return default)
val := ctx.GetArgumentBool(8, true)
if val != true {
t.Errorf("Expected default value true for invalid boolean, got %v", val)
}
}
func TestCommandContext_Messages(t *testing.T) {
ctx := NewCommandContext(CommandTypePlayer, "test", []string{})
// Test adding messages
ctx.AddDefaultMessage("Default message")
ctx.AddErrorMessage("Error message")
ctx.AddStatusMessage("Status message")
ctx.AddMessage(ChannelSay, ColorWhite, "Custom message")
if len(ctx.Messages) != 4 {
t.Errorf("Expected 4 messages, got %d", len(ctx.Messages))
}
// Check message types
expectedMessages := []struct {
channel int
color int
message string
}{
{ChannelDefault, ColorWhite, "Default message"},
{ChannelError, ColorRed, "Error message"},
{ChannelStatus, ColorYellow, "Status message"},
{ChannelSay, ColorWhite, "Custom message"},
}
for i, expected := range expectedMessages {
msg := ctx.Messages[i]
if msg.Channel != expected.channel {
t.Errorf("Message %d: expected channel %d, got %d", i, expected.channel, msg.Channel)
}
if msg.Color != expected.color {
t.Errorf("Message %d: expected color %d, got %d", i, expected.color, msg.Color)
}
if msg.Message != expected.message {
t.Errorf("Message %d: expected message '%s', got '%s'", i, expected.message, msg.Message)
}
}
// Test clearing messages
ctx.ClearMessages()
if len(ctx.Messages) != 0 {
t.Errorf("Expected 0 messages after clearing, got %d", len(ctx.Messages))
}
}
func TestInitializeCommands(t *testing.T) {
cm, err := InitializeCommands()
if err != nil {
t.Fatalf("Failed to initialize commands: %v", err)
}
if cm == nil {
t.Fatal("Expected non-nil command manager")
}
// Test that some basic commands are registered
expectedCommands := []string{"say", "tell", "kick", "ban", "reload", "shutdown", "console_ban", "console_kick"}
for _, cmdName := range expectedCommands {
if !cm.HasCommand(cmdName) {
t.Errorf("Expected command '%s' to be registered", cmdName)
}
}
// Test command counts by type
playerCommands := cm.ListCommandsByType(CommandTypePlayer)
adminCommands := cm.ListCommandsByType(CommandTypeAdmin)
consoleCommands := cm.ListCommandsByType(CommandTypeConsole)
if len(playerCommands) == 0 {
t.Error("Expected player commands to be registered")
}
if len(adminCommands) == 0 {
t.Error("Expected admin commands to be registered")
}
if len(consoleCommands) == 0 {
t.Error("Expected console commands to be registered")
}
}
func TestGetAvailableCommands(t *testing.T) {
cm, err := InitializeCommands()
if err != nil {
t.Fatalf("Failed to initialize commands: %v", err)
}
// Test player level commands
playerCommands := GetAvailableCommands(cm, AdminLevelPlayer)
if len(playerCommands) == 0 {
t.Error("Expected player-level commands to be available")
}
// Test admin level commands
adminCommands := GetAvailableCommands(cm, AdminLevelAdmin)
if len(adminCommands) <= len(playerCommands) {
t.Error("Expected admin to have more commands available than player")
}
// Verify all player commands are also available to admin
playerCommandMap := make(map[string]bool)
for _, cmd := range playerCommands {
playerCommandMap[cmd] = true
}
for _, cmd := range playerCommands {
found := false
for _, adminCmd := range adminCommands {
if adminCmd == cmd {
found = true
break
}
}
if !found {
t.Errorf("Player command '%s' should be available to admin", cmd)
}
}
}

View File

@ -0,0 +1,629 @@
package commands
import (
"fmt"
"strings"
)
// RegisterConsoleCommands registers all console-level commands
func RegisterConsoleCommands(cm *CommandManager) error {
commands := []*Command{
// Player management commands
{
Name: "console_ban",
Type: CommandTypeConsole,
Description: "Bans a player account",
Usage: "console_ban <player> [duration] [reason]",
RequiredLevel: AdminLevelAdmin,
Handler: HandleConsoleBan,
},
{
Name: "console_unban",
Type: CommandTypeConsole,
Description: "Unbans a player account",
Usage: "console_unban <player>",
RequiredLevel: AdminLevelAdmin,
Handler: HandleConsoleUnban,
},
{
Name: "console_kick",
Type: CommandTypeConsole,
Description: "Kicks a player from the server",
Usage: "console_kick <player> [reason]",
RequiredLevel: AdminLevelGM,
Handler: HandleConsoleKick,
},
// Communication commands
{
Name: "console_announce",
Type: CommandTypeConsole,
Description: "Announces a message to all players",
Usage: "console_announce <message>",
RequiredLevel: AdminLevelGM,
Handler: HandleConsoleAnnounce,
},
{
Name: "console_broadcast",
Type: CommandTypeConsole,
Description: "Broadcasts a message to all players",
Usage: "console_broadcast <message>",
RequiredLevel: AdminLevelGM,
Handler: HandleConsoleBroadcast,
},
{
Name: "channel",
Type: CommandTypeConsole,
Description: "Sends message to specific channel",
Usage: "channel <channel_id> <message>",
RequiredLevel: AdminLevelGM,
Handler: HandleConsoleChannel,
},
{
Name: "console_tell",
Type: CommandTypeConsole,
Description: "Sends private message to a player",
Usage: "console_tell <player> <message>",
RequiredLevel: AdminLevelGM,
Handler: HandleConsoleTell,
},
// Information commands
{
Name: "console_guild",
Type: CommandTypeConsole,
Description: "Guild management commands",
Usage: "console_guild <list|info|delete> [options]",
RequiredLevel: AdminLevelGM,
Handler: HandleConsoleGuild,
},
{
Name: "player",
Type: CommandTypeConsole,
Description: "Player information and management",
Usage: "player <list|info|modify> [options]",
RequiredLevel: AdminLevelGM,
Handler: HandleConsolePlayer,
},
{
Name: "setadmin",
Type: CommandTypeConsole,
Description: "Sets admin level for a player",
Usage: "setadmin <player> <admin_level>",
RequiredLevel: AdminLevelAdmin,
Handler: HandleConsoleSetAdmin,
},
{
Name: "world",
Type: CommandTypeConsole,
Description: "World server information",
Usage: "world",
RequiredLevel: AdminLevelGM,
Handler: HandleConsoleWorld,
},
{
Name: "console_zone",
Type: CommandTypeConsole,
Description: "Zone information and management",
Usage: "console_zone <list|info|shutdown> [options]",
RequiredLevel: AdminLevelGM,
Handler: HandleConsoleZone,
},
{
Name: "console_who",
Type: CommandTypeConsole,
Description: "Lists players online",
Usage: "console_who [pattern]",
RequiredLevel: AdminLevelGM,
Handler: HandleConsoleWho,
},
// MOTD commands
{
Name: "getmotd",
Type: CommandTypeConsole,
Description: "Gets the current MOTD",
Usage: "getmotd",
RequiredLevel: AdminLevelGM,
Handler: HandleConsoleGetMOTD,
},
{
Name: "setmotd",
Type: CommandTypeConsole,
Description: "Sets the MOTD",
Usage: "setmotd <message>",
RequiredLevel: AdminLevelAdmin,
Handler: HandleConsoleSetMOTD,
},
// Server management commands
{
Name: "console_reload",
Type: CommandTypeConsole,
Description: "Reloads server data",
Usage: "console_reload <spells|quests|zones|items|rules|all>",
RequiredLevel: AdminLevelAdmin,
Handler: HandleConsoleReload,
},
{
Name: "console_shutdown",
Type: CommandTypeConsole,
Description: "Shuts down the server",
Usage: "console_shutdown [time_minutes] [reason]",
RequiredLevel: AdminLevelAdmin,
Handler: HandleConsoleShutdown,
},
{
Name: "console_cancelshutdown",
Type: CommandTypeConsole,
Description: "Cancels a scheduled shutdown",
Usage: "console_cancelshutdown",
RequiredLevel: AdminLevelAdmin,
Handler: HandleConsoleCancelShutdown,
},
{
Name: "exit",
Type: CommandTypeConsole,
Description: "Exits the server immediately",
Usage: "exit",
RequiredLevel: AdminLevelAdmin,
Handler: HandleConsoleExit,
},
{
Name: "rules",
Type: CommandTypeConsole,
Description: "Rules management commands",
Usage: "rules <list|get|set> [options]",
RequiredLevel: AdminLevelAdmin,
Handler: HandleConsoleRules,
},
// Testing commands
{
Name: "console_test",
Type: CommandTypeConsole,
Description: "Testing command for development",
Usage: "console_test [parameters]",
RequiredLevel: AdminLevelAdmin,
Handler: HandleConsoleTest,
},
}
for _, cmd := range commands {
if err := cm.Register(cmd); err != nil {
return fmt.Errorf("failed to register console command %s: %w", cmd.Name, err)
}
}
return nil
}
// HandleConsoleBan handles the console ban command
func HandleConsoleBan(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: ban <player> [duration] [reason]")
return nil
}
playerName := ctx.Arguments[0]
duration := "permanent"
reason := "Banned by administrator"
if ctx.ArgumentCount() > 1 {
duration = ctx.Arguments[1]
}
if ctx.ArgumentCount() > 2 {
reason = ctx.GetRemainingArguments(2)
}
// TODO: Implement actual ban functionality
ctx.AddStatusMessage(fmt.Sprintf("Banned player '%s' for %s with reason: %s",
playerName, duration, reason))
return nil
}
// HandleConsoleUnban handles the console unban command
func HandleConsoleUnban(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(1, 1); err != nil {
ctx.AddErrorMessage("Usage: unban <player>")
return nil
}
playerName := ctx.Arguments[0]
// TODO: Implement actual unban functionality
ctx.AddStatusMessage(fmt.Sprintf("Unbanned player '%s'", playerName))
return nil
}
// HandleConsoleKick handles the console kick command
func HandleConsoleKick(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: kick <player> [reason]")
return nil
}
playerName := ctx.Arguments[0]
reason := "Kicked by administrator"
if ctx.ArgumentCount() > 1 {
reason = ctx.GetRemainingArguments(1)
}
// TODO: Implement actual kick functionality
ctx.AddStatusMessage(fmt.Sprintf("Kicked player '%s' with reason: %s", playerName, reason))
return nil
}
// HandleConsoleAnnounce handles the console announce command
func HandleConsoleAnnounce(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: announce <message>")
return nil
}
message := ctx.RawArguments
// TODO: Implement server announcement
ctx.AddMessage(ChannelBroadcast, ColorRed, fmt.Sprintf("[ANNOUNCEMENT] %s", message))
return nil
}
// HandleConsoleBroadcast handles the console broadcast command
func HandleConsoleBroadcast(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: broadcast <message>")
return nil
}
message := ctx.RawArguments
// TODO: Implement server-wide broadcast
ctx.AddMessage(ChannelBroadcast, ColorYellow, fmt.Sprintf("[BROADCAST] %s", message))
return nil
}
// HandleConsoleChannel handles the console channel command
func HandleConsoleChannel(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(2, -1); err != nil {
ctx.AddErrorMessage("Usage: channel <channel_id> <message>")
return nil
}
channelID := ctx.GetArgumentInt(0, 0)
message := ctx.GetRemainingArguments(1)
if channelID < 0 {
ctx.AddErrorMessage("Invalid channel ID")
return nil
}
// TODO: Implement channel message sending
ctx.AddMessage(channelID, ColorWhite, message)
ctx.AddStatusMessage(fmt.Sprintf("Sent message to channel %d: %s", channelID, message))
return nil
}
// HandleConsoleTell handles the console tell command
func HandleConsoleTell(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(2, -1); err != nil {
ctx.AddErrorMessage("Usage: tell <player> <message>")
return nil
}
targetName := ctx.Arguments[0]
message := ctx.GetRemainingArguments(1)
// TODO: Implement tell system
ctx.AddStatusMessage(fmt.Sprintf("Told %s: %s", targetName, message))
return nil
}
// HandleConsoleGuild handles the console guild command
func HandleConsoleGuild(ctx *CommandContext) error {
if ctx.ArgumentCount() == 0 {
ctx.AddErrorMessage("Usage: guild <list|info|delete> [options]")
return nil
}
subCommand := strings.ToLower(ctx.Arguments[0])
switch subCommand {
case "list":
// TODO: List all guilds
ctx.AddStatusMessage("Guild listing (not yet implemented)")
case "info":
if ctx.ArgumentCount() < 2 {
ctx.AddErrorMessage("Usage: guild info <guild_name>")
return nil
}
guildName := ctx.Arguments[1]
ctx.AddStatusMessage(fmt.Sprintf("Guild info for '%s' (not yet implemented)", guildName))
case "delete":
if ctx.ArgumentCount() < 2 {
ctx.AddErrorMessage("Usage: guild delete <guild_name>")
return nil
}
guildName := ctx.Arguments[1]
ctx.AddStatusMessage(fmt.Sprintf("Deleted guild '%s' (not yet implemented)", guildName))
default:
ctx.AddErrorMessage("Usage: guild <list|info|delete> [options]")
}
return nil
}
// HandleConsolePlayer handles the console player command
func HandleConsolePlayer(ctx *CommandContext) error {
if ctx.ArgumentCount() == 0 {
ctx.AddErrorMessage("Usage: player <list|info|modify> [options]")
return nil
}
subCommand := strings.ToLower(ctx.Arguments[0])
switch subCommand {
case "list":
// TODO: List all players
ctx.AddStatusMessage("Player listing (not yet implemented)")
case "info":
if ctx.ArgumentCount() < 2 {
ctx.AddErrorMessage("Usage: player info <player_name>")
return nil
}
playerName := ctx.Arguments[1]
ctx.AddStatusMessage(fmt.Sprintf("Player info for '%s' (not yet implemented)", playerName))
case "modify":
if ctx.ArgumentCount() < 4 {
ctx.AddErrorMessage("Usage: player modify <player_name> <attribute> <value>")
return nil
}
playerName := ctx.Arguments[1]
attribute := ctx.Arguments[2]
value := ctx.Arguments[3]
ctx.AddStatusMessage(fmt.Sprintf("Modified %s's %s to %s (not yet implemented)",
playerName, attribute, value))
default:
ctx.AddErrorMessage("Usage: player <list|info|modify> [options]")
}
return nil
}
// HandleConsoleSetAdmin handles the console setadmin command
func HandleConsoleSetAdmin(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(2, 2); err != nil {
ctx.AddErrorMessage("Usage: setadmin <player> <admin_level>")
return nil
}
playerName := ctx.Arguments[0]
adminLevel := ctx.GetArgumentInt(1, 0)
if adminLevel < 0 || adminLevel > 300 {
ctx.AddErrorMessage("Admin level must be between 0 and 300")
return nil
}
// TODO: Implement admin level setting
ctx.AddStatusMessage(fmt.Sprintf("Set %s's admin level to %d", playerName, adminLevel))
return nil
}
// HandleConsoleWorld handles the console world command
func HandleConsoleWorld(ctx *CommandContext) error {
// TODO: Show world server statistics
ctx.AddStatusMessage("World Server Status:")
ctx.AddStatusMessage(" Uptime: 1 hour 23 minutes (not yet implemented)")
ctx.AddStatusMessage(" Players Online: 5 (not yet implemented)")
ctx.AddStatusMessage(" Zones Active: 12 (not yet implemented)")
ctx.AddStatusMessage(" Memory Usage: 256MB (not yet implemented)")
return nil
}
// HandleConsoleZone handles the console zone command
func HandleConsoleZone(ctx *CommandContext) error {
if ctx.ArgumentCount() == 0 {
ctx.AddErrorMessage("Usage: zone <list|info|shutdown> [options]")
return nil
}
subCommand := strings.ToLower(ctx.Arguments[0])
switch subCommand {
case "list":
// TODO: List all zones
ctx.AddStatusMessage("Zone listing (not yet implemented)")
case "info":
if ctx.ArgumentCount() < 2 {
ctx.AddErrorMessage("Usage: zone info <zone_name>")
return nil
}
zoneName := ctx.Arguments[1]
ctx.AddStatusMessage(fmt.Sprintf("Zone info for '%s' (not yet implemented)", zoneName))
case "shutdown":
if ctx.ArgumentCount() < 2 {
ctx.AddErrorMessage("Usage: zone shutdown <zone_name>")
return nil
}
zoneName := ctx.Arguments[1]
ctx.AddStatusMessage(fmt.Sprintf("Shutting down zone '%s' (not yet implemented)", zoneName))
default:
ctx.AddErrorMessage("Usage: zone <list|info|shutdown> [options]")
}
return nil
}
// HandleConsoleWho handles the console who command
func HandleConsoleWho(ctx *CommandContext) error {
pattern := ""
if ctx.ArgumentCount() > 0 {
pattern = ctx.Arguments[0]
}
// TODO: Implement player listing
if pattern != "" {
ctx.AddStatusMessage(fmt.Sprintf("Players matching '%s':", pattern))
} else {
ctx.AddStatusMessage("Players online:")
}
ctx.AddStatusMessage(" TestPlayer (Zone: Qeynos, Level: 10)")
ctx.AddStatusMessage("Total: 1 player online")
return nil
}
// HandleConsoleGetMOTD handles the console getmotd command
func HandleConsoleGetMOTD(ctx *CommandContext) error {
// TODO: Get actual MOTD from database
ctx.AddStatusMessage("Current MOTD:")
ctx.AddStatusMessage(" Welcome to EQ2Go Server!")
return nil
}
// HandleConsoleSetMOTD handles the console setmotd command
func HandleConsoleSetMOTD(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: setmotd <message>")
return nil
}
message := ctx.RawArguments
// TODO: Set MOTD in database
ctx.AddStatusMessage(fmt.Sprintf("MOTD set to: %s", message))
return nil
}
// HandleConsoleReload handles the console reload command
func HandleConsoleReload(ctx *CommandContext) error {
if err := ctx.ValidateArgumentCount(1, 1); err != nil {
ctx.AddErrorMessage("Usage: reload <spells|quests|zones|items|rules|all>")
return nil
}
reloadType := strings.ToLower(ctx.Arguments[0])
switch reloadType {
case "spells":
ctx.AddStatusMessage("Reloaded spells")
case "quests":
ctx.AddStatusMessage("Reloaded quests")
case "zones":
ctx.AddStatusMessage("Reloaded zones")
case "items":
ctx.AddStatusMessage("Reloaded items")
case "rules":
ctx.AddStatusMessage("Reloaded rules")
case "all":
ctx.AddStatusMessage("Reloaded all server data")
default:
ctx.AddErrorMessage("Usage: reload <spells|quests|zones|items|rules|all>")
}
return nil
}
// HandleConsoleShutdown handles the console shutdown command
func HandleConsoleShutdown(ctx *CommandContext) error {
minutes := 5 // Default 5 minutes
reason := "Server maintenance"
if ctx.ArgumentCount() > 0 {
minutes = ctx.GetArgumentInt(0, 5)
}
if ctx.ArgumentCount() > 1 {
reason = ctx.GetRemainingArguments(1)
}
// TODO: Implement server shutdown
ctx.AddStatusMessage(fmt.Sprintf("Server shutdown scheduled in %d minutes. Reason: %s",
minutes, reason))
return nil
}
// HandleConsoleCancelShutdown handles the console cancelshutdown command
func HandleConsoleCancelShutdown(ctx *CommandContext) error {
// TODO: Implement shutdown cancellation
ctx.AddStatusMessage("Server shutdown cancelled")
return nil
}
// HandleConsoleExit handles the console exit command
func HandleConsoleExit(ctx *CommandContext) error {
// TODO: Implement immediate server exit
ctx.AddStatusMessage("Exiting server immediately...")
return nil
}
// HandleConsoleRules handles the console rules command
func HandleConsoleRules(ctx *CommandContext) error {
if ctx.ArgumentCount() == 0 {
ctx.AddErrorMessage("Usage: rules <list|get|set> [options]")
return nil
}
subCommand := strings.ToLower(ctx.Arguments[0])
switch subCommand {
case "list":
// TODO: List all rules
ctx.AddStatusMessage("Server rules listing (not yet implemented)")
case "get":
if ctx.ArgumentCount() < 2 {
ctx.AddErrorMessage("Usage: rules get <rule_name>")
return nil
}
ruleName := ctx.Arguments[1]
ctx.AddStatusMessage(fmt.Sprintf("Rule '%s' value (not yet implemented)", ruleName))
case "set":
if ctx.ArgumentCount() < 3 {
ctx.AddErrorMessage("Usage: rules set <rule_name> <value>")
return nil
}
ruleName := ctx.Arguments[1]
value := ctx.Arguments[2]
ctx.AddStatusMessage(fmt.Sprintf("Set rule '%s' to '%s' (not yet implemented)",
ruleName, value))
default:
ctx.AddErrorMessage("Usage: rules <list|get|set> [options]")
}
return nil
}
// HandleConsoleTest handles the console test command
func HandleConsoleTest(ctx *CommandContext) error {
ctx.AddStatusMessage("Console test command executed successfully")
if ctx.ArgumentCount() > 0 {
ctx.AddStatusMessage(fmt.Sprintf("Arguments: %s", ctx.RawArguments))
}
// TODO: Add actual test functionality for development
return nil
}

View File

@ -0,0 +1,291 @@
package commands
import (
"fmt"
"strconv"
"strings"
"eq2emu/internal/entity"
"eq2emu/internal/spawn"
)
// NewCommandContext creates a new command context
func NewCommandContext(commandType CommandType, commandName string, arguments []string) *CommandContext {
return &CommandContext{
CommandType: commandType,
CommandName: commandName,
Arguments: arguments,
RawArguments: strings.Join(arguments, " "),
Messages: make([]CommandMessage, 0),
Results: make(map[string]any),
}
}
// WithClient adds a client to the context
func (ctx *CommandContext) WithClient(client ClientInterface) *CommandContext {
ctx.Client = client
if client != nil {
ctx.Player = client.GetPlayer()
ctx.Zone = client.GetZone()
ctx.AdminLevel = client.GetAdminLevel()
}
return ctx
}
// WithPlayer adds a player to the context
func (ctx *CommandContext) WithPlayer(player *entity.Entity) *CommandContext {
ctx.Player = player
return ctx
}
// WithTarget adds a target to the context
func (ctx *CommandContext) WithTarget(target *spawn.Spawn) *CommandContext {
ctx.Target = target
return ctx
}
// WithZone adds a zone to the context
func (ctx *CommandContext) WithZone(zone ZoneInterface) *CommandContext {
ctx.Zone = zone
return ctx
}
// WithAdminLevel sets the admin level for the context
func (ctx *CommandContext) WithAdminLevel(level int) *CommandContext {
ctx.AdminLevel = level
return ctx
}
// GetArgument retrieves an argument by index
func (ctx *CommandContext) GetArgument(index int) (string, bool) {
if index < 0 || index >= len(ctx.Arguments) {
return "", false
}
return ctx.Arguments[index], true
}
// GetArgumentInt retrieves an integer argument by index
func (ctx *CommandContext) GetArgumentInt(index int, defaultValue int) int {
if arg, exists := ctx.GetArgument(index); exists {
if value, err := strconv.Atoi(arg); err == nil {
return value
}
}
return defaultValue
}
// GetArgumentFloat retrieves a float argument by index
func (ctx *CommandContext) GetArgumentFloat(index int, defaultValue float64) float64 {
if arg, exists := ctx.GetArgument(index); exists {
if value, err := strconv.ParseFloat(arg, 64); err == nil {
return value
}
}
return defaultValue
}
// GetArgumentBool retrieves a boolean argument by index
func (ctx *CommandContext) GetArgumentBool(index int, defaultValue bool) bool {
if arg, exists := ctx.GetArgument(index); exists {
switch strings.ToLower(arg) {
case "true", "yes", "on", "1":
return true
case "false", "no", "off", "0":
return false
}
}
return defaultValue
}
// GetRemainingArguments gets all arguments from index onwards as a string
func (ctx *CommandContext) GetRemainingArguments(fromIndex int) string {
if fromIndex < 0 || fromIndex >= len(ctx.Arguments) {
return ""
}
return strings.Join(ctx.Arguments[fromIndex:], " ")
}
// HasArgument checks if an argument exists at the given index
func (ctx *CommandContext) HasArgument(index int) bool {
_, exists := ctx.GetArgument(index)
return exists
}
// ArgumentCount returns the number of arguments
func (ctx *CommandContext) ArgumentCount() int {
return len(ctx.Arguments)
}
// AddMessage adds a message to be sent to the client
func (ctx *CommandContext) AddMessage(channel, color int, message string) {
ctx.mutex.Lock()
defer ctx.mutex.Unlock()
ctx.Messages = append(ctx.Messages, CommandMessage{
Channel: channel,
Color: color,
Message: message,
})
}
// AddDefaultMessage adds a message with default channel and color
func (ctx *CommandContext) AddDefaultMessage(message string) {
ctx.AddMessage(ChannelDefault, ColorWhite, message)
}
// AddErrorMessage adds an error message
func (ctx *CommandContext) AddErrorMessage(message string) {
ctx.AddMessage(ChannelError, ColorRed, message)
}
// AddStatusMessage adds a status message
func (ctx *CommandContext) AddStatusMessage(message string) {
ctx.AddMessage(ChannelStatus, ColorYellow, message)
}
// SendMessages sends all queued messages to the client
func (ctx *CommandContext) SendMessages() {
if ctx.Client == nil {
return
}
ctx.mutex.RLock()
messages := make([]CommandMessage, len(ctx.Messages))
copy(messages, ctx.Messages)
ctx.mutex.RUnlock()
for _, msg := range messages {
ctx.Client.SendMessage(msg.Channel, msg.Color, msg.Message)
}
}
// ClearMessages clears all queued messages
func (ctx *CommandContext) ClearMessages() {
ctx.mutex.Lock()
defer ctx.mutex.Unlock()
ctx.Messages = ctx.Messages[:0]
}
// SetResult sets a result value
func (ctx *CommandContext) SetResult(name string, value any) {
ctx.mutex.Lock()
defer ctx.mutex.Unlock()
if ctx.Results == nil {
ctx.Results = make(map[string]any)
}
ctx.Results[name] = value
}
// GetResult retrieves a result value
func (ctx *CommandContext) GetResult(name string) (any, bool) {
ctx.mutex.RLock()
defer ctx.mutex.RUnlock()
value, exists := ctx.Results[name]
return value, exists
}
// GetResultString retrieves a string result
func (ctx *CommandContext) GetResultString(name string, defaultValue string) string {
if value, exists := ctx.GetResult(name); exists {
if str, ok := value.(string); ok {
return str
}
}
return defaultValue
}
// GetResultInt retrieves an integer result
func (ctx *CommandContext) GetResultInt(name string, defaultValue int) int {
if value, exists := ctx.GetResult(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
}
// ValidateArgumentCount validates that the command has the required number of arguments
func (ctx *CommandContext) ValidateArgumentCount(min, max int) error {
argCount := len(ctx.Arguments)
if argCount < min {
return fmt.Errorf("command '%s' requires at least %d arguments, got %d", ctx.CommandName, min, argCount)
}
if max >= 0 && argCount > max {
return fmt.Errorf("command '%s' accepts at most %d arguments, got %d", ctx.CommandName, max, argCount)
}
return nil
}
// RequirePlayer ensures a player is present in the context
func (ctx *CommandContext) RequirePlayer() error {
if ctx.Player == nil {
return fmt.Errorf("command '%s' requires a player", ctx.CommandName)
}
return nil
}
// RequireTarget ensures a target is present in the context
func (ctx *CommandContext) RequireTarget() error {
if ctx.Target == nil {
return fmt.Errorf("command '%s' requires a target", ctx.CommandName)
}
return nil
}
// RequireZone ensures a zone is present in the context
func (ctx *CommandContext) RequireZone() error {
if ctx.Zone == nil {
return fmt.Errorf("command '%s' requires a zone", ctx.CommandName)
}
return nil
}
// RequireAdminLevel ensures the user has the required admin level
func (ctx *CommandContext) RequireAdminLevel(requiredLevel int) error {
if ctx.AdminLevel < requiredLevel {
return fmt.Errorf("command '%s' requires admin level %d, you have %d",
ctx.CommandName, requiredLevel, ctx.AdminLevel)
}
return nil
}
// GetPlayerName safely gets the player's name
func (ctx *CommandContext) GetPlayerName() string {
if ctx.Player != nil {
return ctx.Player.GetName()
}
if ctx.Client != nil {
return ctx.Client.GetName()
}
return "Unknown"
}
// GetTargetName safely gets the target's name
func (ctx *CommandContext) GetTargetName() string {
if ctx.Target != nil {
return ctx.Target.GetName()
}
return "No Target"
}
// GetZoneName safely gets the zone's name
func (ctx *CommandContext) GetZoneName() string {
if ctx.Zone != nil {
return ctx.Zone.GetName()
}
return "No Zone"
}

View File

@ -0,0 +1,247 @@
package commands
import (
"context"
"fmt"
"strings"
)
// NewCommandManager creates a new command manager instance
func NewCommandManager() *CommandManager {
return &CommandManager{
commands: make(map[string]*Command),
}
}
// Register registers a command with the manager
func (cm *CommandManager) Register(command *Command) error {
if command == nil {
return fmt.Errorf("command cannot be nil")
}
if command.Name == "" {
return fmt.Errorf("command name cannot be empty")
}
if command.Handler == nil {
return fmt.Errorf("command handler cannot be nil")
}
cm.mutex.Lock()
defer cm.mutex.Unlock()
// Normalize command name to lowercase for case-insensitive matching
normalizedName := strings.ToLower(command.Name)
if _, exists := cm.commands[normalizedName]; exists {
return fmt.Errorf("command '%s' is already registered", command.Name)
}
cm.commands[normalizedName] = command
return nil
}
// RegisterSubCommand registers a subcommand under a parent command
func (cm *CommandManager) RegisterSubCommand(parentName string, subCommand *Command) error {
if subCommand == nil {
return fmt.Errorf("subcommand cannot be nil")
}
cm.mutex.Lock()
defer cm.mutex.Unlock()
normalizedParent := strings.ToLower(parentName)
parentCmd, exists := cm.commands[normalizedParent]
if !exists {
return fmt.Errorf("parent command '%s' not found", parentName)
}
if parentCmd.SubCommands == nil {
parentCmd.SubCommands = make(map[string]*Command)
}
normalizedSubName := strings.ToLower(subCommand.Name)
if _, exists := parentCmd.SubCommands[normalizedSubName]; exists {
return fmt.Errorf("subcommand '%s' already exists under '%s'", subCommand.Name, parentName)
}
parentCmd.SubCommands[normalizedSubName] = subCommand
return nil
}
// Unregister removes a command from the manager
func (cm *CommandManager) Unregister(name string) {
cm.mutex.Lock()
defer cm.mutex.Unlock()
normalizedName := strings.ToLower(name)
delete(cm.commands, normalizedName)
}
// HasCommand checks if a command is registered
func (cm *CommandManager) HasCommand(name string) bool {
cm.mutex.RLock()
defer cm.mutex.RUnlock()
normalizedName := strings.ToLower(name)
_, exists := cm.commands[normalizedName]
return exists
}
// GetCommand retrieves a command by name
func (cm *CommandManager) GetCommand(name string) (*Command, bool) {
cm.mutex.RLock()
defer cm.mutex.RUnlock()
normalizedName := strings.ToLower(name)
command, exists := cm.commands[normalizedName]
return command, exists
}
// ListCommands returns all registered command names
func (cm *CommandManager) ListCommands() []string {
cm.mutex.RLock()
defer cm.mutex.RUnlock()
names := make([]string, 0, len(cm.commands))
for name := range cm.commands {
names = append(names, name)
}
return names
}
// ListCommandsByType returns commands filtered by type
func (cm *CommandManager) ListCommandsByType(commandType CommandType) []*Command {
cm.mutex.RLock()
defer cm.mutex.RUnlock()
commands := make([]*Command, 0)
for _, cmd := range cm.commands {
if cmd.Type == commandType {
commands = append(commands, cmd)
}
}
return commands
}
// Execute executes a command with the given context
func (cm *CommandManager) Execute(ctx *CommandContext) error {
if ctx == nil {
return fmt.Errorf("command context cannot be nil")
}
if ctx.CommandName == "" {
return fmt.Errorf("command name cannot be empty")
}
// Find the command
normalizedName := strings.ToLower(ctx.CommandName)
cm.mutex.RLock()
command, exists := cm.commands[normalizedName]
cm.mutex.RUnlock()
if !exists {
return fmt.Errorf("unknown command: %s", ctx.CommandName)
}
// Check admin level requirements
if ctx.AdminLevel < command.RequiredLevel {
return fmt.Errorf("insufficient privileges for command '%s' (required: %d, have: %d)",
command.Name, command.RequiredLevel, ctx.AdminLevel)
}
// Check for subcommands
if len(ctx.Arguments) > 0 && command.SubCommands != nil {
normalizedSubName := strings.ToLower(ctx.Arguments[0])
if subCommand, exists := command.SubCommands[normalizedSubName]; exists {
// Execute subcommand
subCtx := &CommandContext{
Context: ctx.Context,
CommandType: subCommand.Type,
CommandName: subCommand.Name,
Arguments: ctx.Arguments[1:], // Remove subcommand name from arguments
RawArguments: strings.TrimSpace(strings.Join(ctx.Arguments[1:], " ")),
AdminLevel: ctx.AdminLevel,
RequiredLevel: subCommand.RequiredLevel,
Client: ctx.Client,
Player: ctx.Player,
Target: ctx.Target,
Zone: ctx.Zone,
Messages: make([]CommandMessage, 0),
Results: make(map[string]any),
}
// Check subcommand admin level
if ctx.AdminLevel < subCommand.RequiredLevel {
return fmt.Errorf("insufficient privileges for subcommand '%s %s' (required: %d, have: %d)",
command.Name, subCommand.Name, subCommand.RequiredLevel, ctx.AdminLevel)
}
return subCommand.Handler(subCtx)
}
}
// Execute main command
return command.Handler(ctx)
}
// ParseCommand parses a raw command string into a CommandContext
func (cm *CommandManager) ParseCommand(rawCommand string, client ClientInterface) *CommandContext {
// Remove leading slash if present
rawCommand = strings.TrimSpace(rawCommand)
rawCommand = strings.TrimPrefix(rawCommand, "/")
// Split into command and arguments
parts := strings.Fields(rawCommand)
if len(parts) == 0 {
return nil
}
commandName := parts[0]
arguments := parts[1:]
rawArguments := strings.TrimSpace(strings.Join(arguments, " "))
// Determine admin level
adminLevel := AdminLevelPlayer
if client != nil {
adminLevel = client.GetAdminLevel()
}
// Create context
ctx := &CommandContext{
Context: context.Background(),
CommandName: commandName,
Arguments: arguments,
RawArguments: rawArguments,
AdminLevel: adminLevel,
Client: client,
Messages: make([]CommandMessage, 0),
Results: make(map[string]any),
}
// Set additional context fields if client is available
if client != nil {
ctx.Player = client.GetPlayer()
ctx.Zone = client.GetZone()
}
// Determine command type based on name
ctx.CommandType = cm.determineCommandType(commandName)
return ctx
}
// determineCommandType determines the command type based on the command name
func (cm *CommandManager) determineCommandType(commandName string) CommandType {
// Check if command exists and get its type
cm.mutex.RLock()
defer cm.mutex.RUnlock()
normalizedName := strings.ToLower(commandName)
if command, exists := cm.commands[normalizedName]; exists {
return command.Type
}
// Default to player command if not found
return CommandTypePlayer
}

727
internal/commands/player.go Normal file
View File

@ -0,0 +1,727 @@
package commands
import (
"fmt"
"strings"
)
// RegisterPlayerCommands registers all player-level commands
func RegisterPlayerCommands(cm *CommandManager) error {
commands := []*Command{
// Communication commands
{
Name: "say",
Type: CommandTypePlayer,
Description: "Says something to nearby players",
Usage: "/say <message>",
RequiredLevel: AdminLevelPlayer,
Handler: HandleSay,
},
{
Name: "tell",
Type: CommandTypePlayer,
Description: "Sends a private message to a player",
Usage: "/tell <player> <message>",
RequiredLevel: AdminLevelPlayer,
Handler: HandleTell,
},
{
Name: "yell",
Type: CommandTypePlayer,
Description: "Yells something to a wider area",
Usage: "/yell <message>",
RequiredLevel: AdminLevelPlayer,
Handler: HandleYell,
},
{
Name: "shout",
Type: CommandTypePlayer,
Description: "Shouts something zone-wide",
Usage: "/shout <message>",
RequiredLevel: AdminLevelPlayer,
Handler: HandleShout,
},
{
Name: "ooc",
Type: CommandTypePlayer,
Description: "Sends an out-of-character message",
Usage: "/ooc <message>",
RequiredLevel: AdminLevelPlayer,
Handler: HandleOOC,
},
{
Name: "emote",
Type: CommandTypePlayer,
Description: "Performs an emote",
Usage: "/emote <action>",
RequiredLevel: AdminLevelPlayer,
Handler: HandleEmote,
},
// Group commands
{
Name: "group",
Type: CommandTypePlayer,
Description: "Group management commands",
Usage: "/group <invite|leave|kick|disband> [player]",
RequiredLevel: AdminLevelPlayer,
Handler: HandleGroup,
},
{
Name: "groupsay",
Type: CommandTypePlayer,
Description: "Says something to your group",
Usage: "/groupsay <message>",
RequiredLevel: AdminLevelPlayer,
Handler: HandleGroupSay,
},
{
Name: "gsay",
Type: CommandTypePlayer,
Description: "Says something to your group (short form)",
Usage: "/gsay <message>",
RequiredLevel: AdminLevelPlayer,
Handler: HandleGroupSay,
},
// Guild commands
{
Name: "guild",
Type: CommandTypePlayer,
Description: "Guild management commands",
Usage: "/guild <invite|leave|promote|demote|kick> [player]",
RequiredLevel: AdminLevelPlayer,
Handler: HandleGuild,
},
{
Name: "guildsay",
Type: CommandTypePlayer,
Description: "Says something to your guild",
Usage: "/guildsay <message>",
RequiredLevel: AdminLevelPlayer,
Handler: HandleGuildSay,
},
{
Name: "officersay",
Type: CommandTypePlayer,
Description: "Says something to guild officers",
Usage: "/officersay <message>",
RequiredLevel: AdminLevelPlayer,
Handler: HandleOfficerSay,
},
// Character commands
{
Name: "who",
Type: CommandTypePlayer,
Description: "Lists players online",
Usage: "/who [name pattern]",
RequiredLevel: AdminLevelPlayer,
Handler: HandleWho,
},
{
Name: "time",
Type: CommandTypePlayer,
Description: "Shows current game time",
Usage: "/time",
RequiredLevel: AdminLevelPlayer,
Handler: HandleTime,
},
{
Name: "afk",
Type: CommandTypePlayer,
Description: "Toggles AFK status",
Usage: "/afk [message]",
RequiredLevel: AdminLevelPlayer,
Handler: HandleAFK,
},
{
Name: "anon",
Type: CommandTypePlayer,
Description: "Toggles anonymous status",
Usage: "/anon",
RequiredLevel: AdminLevelPlayer,
Handler: HandleAnonymous,
},
{
Name: "lfg",
Type: CommandTypePlayer,
Description: "Toggles looking for group status",
Usage: "/lfg",
RequiredLevel: AdminLevelPlayer,
Handler: HandleLFG,
},
{
Name: "location",
Type: CommandTypePlayer,
Description: "Shows current location",
Usage: "/location",
RequiredLevel: AdminLevelPlayer,
Handler: HandleLocation,
},
// Item commands
{
Name: "inventory",
Type: CommandTypePlayer,
Description: "Shows inventory information",
Usage: "/inventory",
RequiredLevel: AdminLevelPlayer,
Handler: HandleInventory,
},
{
Name: "consider",
Type: CommandTypePlayer,
Description: "Considers a target",
Usage: "/consider [target]",
RequiredLevel: AdminLevelPlayer,
Handler: HandleConsider,
},
// Trade commands
{
Name: "trade",
Type: CommandTypePlayer,
Description: "Trade management",
Usage: "/trade <start|accept|reject|cancel> [player]",
RequiredLevel: AdminLevelPlayer,
Handler: HandleTrade,
},
// Quest commands
{
Name: "quest",
Type: CommandTypePlayer,
Description: "Quest management",
Usage: "/quest <list|abandon|share> [quest_id]",
RequiredLevel: AdminLevelPlayer,
Handler: HandleQuest,
},
// Misc commands
{
Name: "help",
Type: CommandTypePlayer,
Description: "Shows available commands",
Usage: "/help [command]",
RequiredLevel: AdminLevelPlayer,
Handler: HandleHelp,
},
{
Name: "quit",
Type: CommandTypePlayer,
Description: "Safely logs out of the game",
Usage: "/quit",
RequiredLevel: AdminLevelPlayer,
Handler: HandleQuit,
},
}
for _, cmd := range commands {
if err := cm.Register(cmd); err != nil {
return fmt.Errorf("failed to register command %s: %w", cmd.Name, err)
}
}
return nil
}
// HandleSay handles the /say command
func HandleSay(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: /say <message>")
return nil
}
message := ctx.RawArguments
// TODO: Implement actual zone-wide say broadcast
// For now, just confirm to the player
ctx.AddMessage(ChannelSay, ColorWhite, fmt.Sprintf("You say, \"%s\"", message))
// TODO: Send to nearby players in zone
// zone.SendNearbyMessage(ctx.Player.GetPosition(), ChannelSay,
// fmt.Sprintf("%s says, \"%s\"", playerName, message))
return nil
}
// HandleTell handles the /tell command
func HandleTell(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if err := ctx.ValidateArgumentCount(2, -1); err != nil {
ctx.AddErrorMessage("Usage: /tell <player> <message>")
return nil
}
targetName := ctx.Arguments[0]
message := ctx.GetRemainingArguments(1)
// TODO: Implement actual tell system
// For now, just show what would be sent
ctx.AddMessage(ChannelPrivateTell, ColorWhite,
fmt.Sprintf("You tell %s, \"%s\"", targetName, message))
// TODO: Find target player and send message
// if targetPlayer := world.FindPlayerByName(targetName); targetPlayer != nil {
// targetPlayer.SendMessage(ChannelPrivateTell,
// fmt.Sprintf("%s tells you, \"%s\"", playerName, message))
// } else {
// ctx.AddErrorMessage(fmt.Sprintf("Player '%s' not found", targetName))
// }
return nil
}
// HandleYell handles the /yell command
func HandleYell(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: /yell <message>")
return nil
}
message := ctx.RawArguments
ctx.AddMessage(ChannelYell, ColorWhite, fmt.Sprintf("You yell, \"%s\"", message))
// TODO: Send to wider area in zone
return nil
}
// HandleShout handles the /shout command
func HandleShout(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: /shout <message>")
return nil
}
message := ctx.RawArguments
ctx.AddMessage(ChannelShout, ColorWhite, fmt.Sprintf("You shout, \"%s\"", message))
// TODO: Send zone-wide
return nil
}
// HandleOOC handles the /ooc command
func HandleOOC(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: /ooc <message>")
return nil
}
message := ctx.RawArguments
playerName := ctx.GetPlayerName()
ctx.AddMessage(ChannelOutOfCharacter, ColorWhite,
fmt.Sprintf("[OOC] %s: %s", playerName, message))
// TODO: Send to zone OOC channel
return nil
}
// HandleEmote handles the /emote command
func HandleEmote(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: /emote <action>")
return nil
}
action := ctx.RawArguments
playerName := ctx.GetPlayerName()
ctx.AddMessage(ChannelEmote, ColorWhite, fmt.Sprintf("%s %s", playerName, action))
// TODO: Send emote to nearby players
return nil
}
// HandleGroup handles the /group command
func HandleGroup(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if ctx.ArgumentCount() == 0 {
ctx.AddErrorMessage("Usage: /group <invite|leave|kick|disband> [player]")
return nil
}
subCommand := strings.ToLower(ctx.Arguments[0])
switch subCommand {
case "invite":
if ctx.ArgumentCount() < 2 {
ctx.AddErrorMessage("Usage: /group invite <player>")
return nil
}
targetName := ctx.Arguments[1]
// TODO: Implement group invite
ctx.AddStatusMessage(fmt.Sprintf("Invited %s to group", targetName))
case "leave":
// TODO: Implement group leave
ctx.AddStatusMessage("Left group")
case "kick":
if ctx.ArgumentCount() < 2 {
ctx.AddErrorMessage("Usage: /group kick <player>")
return nil
}
targetName := ctx.Arguments[1]
// TODO: Implement group kick
ctx.AddStatusMessage(fmt.Sprintf("Kicked %s from group", targetName))
case "disband":
// TODO: Implement group disband
ctx.AddStatusMessage("Disbanded group")
default:
ctx.AddErrorMessage("Usage: /group <invite|leave|kick|disband> [player]")
}
return nil
}
// HandleGroupSay handles the /groupsay and /gsay commands
func HandleGroupSay(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: /groupsay <message>")
return nil
}
message := ctx.RawArguments
playerName := ctx.GetPlayerName()
// TODO: Check if player is in a group
ctx.AddMessage(ChannelGroupSay, ColorWhite,
fmt.Sprintf("[Group] %s: %s", playerName, message))
// TODO: Send to group members
return nil
}
// HandleGuild handles the /guild command
func HandleGuild(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if ctx.ArgumentCount() == 0 {
// TODO: Show guild information
ctx.AddStatusMessage("Guild information (not yet implemented)")
return nil
}
subCommand := strings.ToLower(ctx.Arguments[0])
switch subCommand {
case "invite":
if ctx.ArgumentCount() < 2 {
ctx.AddErrorMessage("Usage: /guild invite <player>")
return nil
}
targetName := ctx.Arguments[1]
// TODO: Implement guild invite
ctx.AddStatusMessage(fmt.Sprintf("Invited %s to guild", targetName))
case "leave":
// TODO: Implement guild leave
ctx.AddStatusMessage("Left guild")
default:
ctx.AddErrorMessage("Usage: /guild <invite|leave|promote|demote|kick> [player]")
}
return nil
}
// HandleGuildSay handles the /guildsay command
func HandleGuildSay(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: /guildsay <message>")
return nil
}
message := ctx.RawArguments
playerName := ctx.GetPlayerName()
// TODO: Check if player is in a guild
ctx.AddMessage(ChannelGuildSay, ColorWhite,
fmt.Sprintf("[Guild] %s: %s", playerName, message))
return nil
}
// HandleOfficerSay handles the /officersay command
func HandleOfficerSay(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if err := ctx.ValidateArgumentCount(1, -1); err != nil {
ctx.AddErrorMessage("Usage: /officersay <message>")
return nil
}
message := ctx.RawArguments
playerName := ctx.GetPlayerName()
// TODO: Check if player is guild officer
ctx.AddMessage(ChannelOfficerSay, ColorWhite,
fmt.Sprintf("[Officer] %s: %s", playerName, message))
return nil
}
// HandleWho handles the /who command
func HandleWho(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
// TODO: Implement player listing
ctx.AddMessage(ChannelWho, ColorWhite, "Players online:")
ctx.AddMessage(ChannelWho, ColorWhite, fmt.Sprintf(" %s (You)", ctx.GetPlayerName()))
ctx.AddMessage(ChannelWho, ColorWhite, "Total: 1 player online")
return nil
}
// HandleTime handles the /time command
func HandleTime(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
// TODO: Implement game time system
ctx.AddStatusMessage("Game time: 12:00 PM (not yet implemented)")
return nil
}
// HandleAFK handles the /afk command
func HandleAFK(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
afkMessage := ""
if ctx.ArgumentCount() > 0 {
afkMessage = ctx.RawArguments
}
// TODO: Toggle AFK status
if afkMessage != "" {
ctx.AddStatusMessage(fmt.Sprintf("AFK status set with message: %s", afkMessage))
} else {
ctx.AddStatusMessage("AFK status toggled")
}
return nil
}
// HandleAnonymous handles the /anon command
func HandleAnonymous(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
// TODO: Toggle anonymous status
ctx.AddStatusMessage("Anonymous status toggled")
return nil
}
// HandleLFG handles the /lfg command
func HandleLFG(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
// TODO: Toggle LFG status
ctx.AddStatusMessage("Looking for group status toggled")
return nil
}
// HandleLocation handles the /location command
func HandleLocation(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
// TODO: Get actual player position
zoneName := ctx.GetZoneName()
ctx.AddStatusMessage(fmt.Sprintf("Location: %s (coordinates not yet implemented)", zoneName))
return nil
}
// HandleInventory handles the /inventory command
func HandleInventory(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
// TODO: Show inventory information
ctx.AddStatusMessage("Inventory information (not yet implemented)")
return nil
}
// HandleConsider handles the /consider command
func HandleConsider(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
targetName := ctx.GetTargetName()
if targetName == "No Target" && ctx.ArgumentCount() > 0 {
targetName = ctx.Arguments[0]
}
if targetName == "No Target" {
ctx.AddErrorMessage("You must target something or specify a target name")
return nil
}
// TODO: Implement consider system
ctx.AddStatusMessage(fmt.Sprintf("You consider %s... (not yet implemented)", targetName))
return nil
}
// HandleTrade handles the /trade command
func HandleTrade(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if ctx.ArgumentCount() == 0 {
ctx.AddErrorMessage("Usage: /trade <start|accept|reject|cancel> [player]")
return nil
}
subCommand := strings.ToLower(ctx.Arguments[0])
switch subCommand {
case "start":
if ctx.ArgumentCount() < 2 {
ctx.AddErrorMessage("Usage: /trade start <player>")
return nil
}
targetName := ctx.Arguments[1]
ctx.AddStatusMessage(fmt.Sprintf("Trade initiated with %s", targetName))
case "accept":
ctx.AddStatusMessage("Trade accepted")
case "reject":
ctx.AddStatusMessage("Trade rejected")
case "cancel":
ctx.AddStatusMessage("Trade cancelled")
default:
ctx.AddErrorMessage("Usage: /trade <start|accept|reject|cancel> [player]")
}
return nil
}
// HandleQuest handles the /quest command
func HandleQuest(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
if ctx.ArgumentCount() == 0 {
ctx.AddErrorMessage("Usage: /quest <list|abandon|share> [quest_id]")
return nil
}
subCommand := strings.ToLower(ctx.Arguments[0])
switch subCommand {
case "list":
// TODO: List active quests
ctx.AddStatusMessage("Active quests (not yet implemented)")
case "abandon":
if ctx.ArgumentCount() < 2 {
ctx.AddErrorMessage("Usage: /quest abandon <quest_id>")
return nil
}
questID := ctx.Arguments[1]
ctx.AddStatusMessage(fmt.Sprintf("Abandoned quest %s", questID))
case "share":
if ctx.ArgumentCount() < 2 {
ctx.AddErrorMessage("Usage: /quest share <quest_id>")
return nil
}
questID := ctx.Arguments[1]
ctx.AddStatusMessage(fmt.Sprintf("Shared quest %s", questID))
default:
ctx.AddErrorMessage("Usage: /quest <list|abandon|share> [quest_id]")
}
return nil
}
// HandleHelp handles the /help command
func HandleHelp(ctx *CommandContext) error {
// TODO: Show available commands based on admin level
ctx.AddMessage(ChannelCommands, ColorWhite, "Available commands:")
ctx.AddMessage(ChannelCommands, ColorWhite, "/say <message> - Say something to nearby players")
ctx.AddMessage(ChannelCommands, ColorWhite, "/tell <player> <message> - Send private message")
ctx.AddMessage(ChannelCommands, ColorWhite, "/who - List online players")
ctx.AddMessage(ChannelCommands, ColorWhite, "/time - Show game time")
ctx.AddMessage(ChannelCommands, ColorWhite, "/location - Show current location")
ctx.AddMessage(ChannelCommands, ColorWhite, "/help - Show this help")
return nil
}
// HandleQuit handles the /quit command
func HandleQuit(ctx *CommandContext) error {
if err := ctx.RequirePlayer(); err != nil {
return err
}
ctx.AddStatusMessage("Logging out safely...")
// TODO: Implement safe logout
// if ctx.Client != nil {
// ctx.Client.Disconnect()
// }
return nil
}

189
internal/commands/types.go Normal file
View File

@ -0,0 +1,189 @@
package commands
import (
"context"
"sync"
"eq2emu/internal/entity"
"eq2emu/internal/spawn"
)
// CommandType represents different types of commands
type CommandType int
const (
CommandTypePlayer CommandType = iota // Player commands (/say, /tell, etc.)
CommandTypeAdmin // Admin commands (/kick, /ban, etc.)
CommandTypeConsole // Console commands (shutdown, reload, etc.)
CommandTypeSpawn // Spawn manipulation commands
CommandTypeZone // Zone management commands
CommandTypeGuild // Guild commands
CommandTypeItem // Item commands
CommandTypeQuest // Quest commands
)
func (ct CommandType) String() string {
switch ct {
case CommandTypePlayer:
return "player"
case CommandTypeAdmin:
return "admin"
case CommandTypeConsole:
return "console"
case CommandTypeSpawn:
return "spawn"
case CommandTypeZone:
return "zone"
case CommandTypeGuild:
return "guild"
case CommandTypeItem:
return "item"
case CommandTypeQuest:
return "quest"
default:
return "unknown"
}
}
// CommandContext provides context for command execution
type CommandContext struct {
// Core context
Context context.Context
// Command information
CommandType CommandType
CommandName string
Arguments []string
RawArguments string
AdminLevel int
RequiredLevel int
// Game objects (nil if not applicable)
Client ClientInterface
Player *entity.Entity
Target *spawn.Spawn
Zone ZoneInterface
// Results and messages
Messages []CommandMessage
Results map[string]any
// Synchronization
mutex sync.RWMutex
}
// CommandMessage represents a message to send to the client
type CommandMessage struct {
Channel int
Color int
Message string
}
// CommandFunction represents a callable command function
type CommandFunction func(ctx *CommandContext) error
// Command represents a registered command
type Command struct {
Name string
Type CommandType
Description string
Usage string
RequiredLevel int
Handler CommandFunction
SubCommands map[string]*Command
}
// CommandManager manages command registration and execution
type CommandManager struct {
commands map[string]*Command
mutex sync.RWMutex
}
// ClientInterface defines the interface for client interactions
type ClientInterface interface {
GetPlayer() *entity.Entity
GetAccountID() int32
GetCharacterID() int32
GetAdminLevel() int
GetName() string
IsInZone() bool
GetZone() ZoneInterface
SendMessage(channel int, color int, message string)
SendPopupMessage(message string)
Disconnect()
}
// ZoneInterface defines the interface for zone interactions
type ZoneInterface interface {
GetID() int32
GetName() string
GetDescription() string
GetPlayers() []*entity.Entity
Shutdown()
SendZoneMessage(channel int, color int, message string)
GetSpawnByName(name string) *spawn.Spawn
GetSpawnByID(id int32) *spawn.Spawn
}
// Chat channel constants (from C++ Commands.h)
const (
ChannelAllText = 0
ChannelGameText = 1
ChannelDefault = 2
ChannelError = 3
ChannelStatus = 4
ChannelMOTD = 5
ChannelChatText = 6
ChannelNearbyCHat = 7
ChannelSay = 8
ChannelShout = 9
ChannelEmote = 10
ChannelYell = 11
ChannelNarrative = 12
ChannelNonPlayerSay = 13
ChannelGroupChat = 14
ChannelGroupSay = 15
ChannelRaidSay = 16
ChannelGuildChat = 17
ChannelGuildSay = 18
ChannelOfficerSay = 19
ChannelGuildMOTD = 20
ChannelPrivateChat = 25
ChannelPrivateTell = 28
ChannelOutOfCharacter = 32
ChannelBroadcast = 92
ChannelWho = 93
ChannelCommands = 94
)
// Chat color constants (from C++ Commands.h)
const (
ColorRed = 3
ColorChatRelation = 4
ColorYellow = 5
ColorNewLoot = 84
ColorNewestLoot = 89
ColorWhite = 254
)
// Admin level constants
const (
AdminLevelPlayer = 0
AdminLevelGuide = 100
AdminLevelGM = 200
AdminLevelAdmin = 300
)
// ErrorHandler defines how command errors should be handled
type ErrorHandler interface {
HandleCommandError(ctx *CommandContext, err error)
}
// CommandLogger defines logging interface for commands
type CommandLogger interface {
LogCommand(ctx *CommandContext, success bool, err error)
Debug(msg string, args ...any)
Info(msg string, args ...any)
Warn(msg string, args ...any)
Error(msg string, args ...any)
}

View File

@ -16,8 +16,8 @@ func NewEventContext(eventType EventType, eventName, functionName string) *Event
EventType: eventType,
EventName: eventName,
FunctionName: functionName,
Parameters: make(map[string]interface{}),
Results: make(map[string]interface{}),
Parameters: make(map[string]any),
Results: make(map[string]any),
}
}
@ -46,19 +46,19 @@ func (ctx *EventContext) WithQuest(quest *quests.Quest) *EventContext {
}
// WithParameter adds a parameter to the context
func (ctx *EventContext) WithParameter(name string, value interface{}) *EventContext {
func (ctx *EventContext) WithParameter(name string, value any) *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 {
func (ctx *EventContext) WithParameters(params map[string]any) *EventContext {
ctx.mutex.Lock()
defer ctx.mutex.Unlock()
for k, v := range params {
ctx.Parameters[k] = v
}
@ -86,10 +86,10 @@ func (ctx *EventContext) GetQuest() *quests.Quest {
}
// GetParameter retrieves a parameter
func (ctx *EventContext) GetParameter(name string) (interface{}, bool) {
func (ctx *EventContext) GetParameter(name string) (any, bool) {
ctx.mutex.RLock()
defer ctx.mutex.RUnlock()
value, exists := ctx.Parameters[name]
return value, exists
}
@ -153,41 +153,41 @@ func (ctx *EventContext) GetParameterBool(name string, defaultValue bool) bool {
}
// SetResult sets a result value
func (ctx *EventContext) SetResult(name string, value interface{}) {
func (ctx *EventContext) SetResult(name string, value any) {
ctx.mutex.Lock()
defer ctx.mutex.Unlock()
if ctx.Results == nil {
ctx.Results = make(map[string]interface{})
ctx.Results = make(map[string]any)
}
ctx.Results[name] = value
}
// GetResult retrieves a result value
func (ctx *EventContext) GetResult(name string) (interface{}, bool) {
func (ctx *EventContext) GetResult(name string) (any, 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{}) {
func (ctx *EventContext) Debug(msg string, args ...any) {
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{}) {
func (ctx *EventContext) Info(msg string, args ...any) {
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{}) {
func (ctx *EventContext) Warn(msg string, args ...any) {
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{}) {
func (ctx *EventContext) Error(msg string, args ...any) {
fmt.Printf("[ERROR] [%s:%s] %s\n", ctx.EventName, ctx.FunctionName, fmt.Sprintf(msg, args...))
}
}

View File

@ -15,10 +15,7 @@ func SetInt(ctx *events.EventContext) error {
return fmt.Errorf("no spawn in context")
}
value := ctx.GetParameterInt("value", 0)
if value < 0 {
value = 0
}
value := max(ctx.GetParameterInt("value", 0), 0)
// TODO: Implement INT stat when InfoStruct is available
ctx.Debug("Set INT to %d for spawn %s (not yet implemented)", value, spawn.GetName())
@ -33,10 +30,7 @@ func SetWis(ctx *events.EventContext) error {
return fmt.Errorf("no spawn in context")
}
value := ctx.GetParameterInt("value", 0)
if value < 0 {
value = 0
}
value := max(ctx.GetParameterInt("value", 0), 0)
// TODO: Implement WIS stat when InfoStruct is available
ctx.Debug("Set WIS to %d for spawn %s (not yet implemented)", value, spawn.GetName())
@ -51,10 +45,7 @@ func SetSta(ctx *events.EventContext) error {
return fmt.Errorf("no spawn in context")
}
value := ctx.GetParameterInt("value", 0)
if value < 0 {
value = 0
}
value := max(ctx.GetParameterInt("value", 0), 0)
// TODO: Implement STA stat when InfoStruct is available
ctx.Debug("Set STA to %d for spawn %s (not yet implemented)", value, spawn.GetName())
@ -69,10 +60,7 @@ func SetStr(ctx *events.EventContext) error {
return fmt.Errorf("no spawn in context")
}
value := ctx.GetParameterInt("value", 0)
if value < 0 {
value = 0
}
value := max(ctx.GetParameterInt("value", 0), 0)
// TODO: Implement STR stat when InfoStruct is available
ctx.Debug("Set STR to %d for spawn %s (not yet implemented)", value, spawn.GetName())
@ -87,10 +75,7 @@ func SetAgi(ctx *events.EventContext) error {
return fmt.Errorf("no spawn in context")
}
value := ctx.GetParameterInt("value", 0)
if value < 0 {
value = 0
}
value := max(ctx.GetParameterInt("value", 0), 0)
// TODO: Implement AGI stat when InfoStruct is available
ctx.Debug("Set AGI to %d for spawn %s (not yet implemented)", value, spawn.GetName())
@ -236,13 +221,7 @@ func SetLevel(ctx *events.EventContext) error {
return fmt.Errorf("no spawn in context")
}
level := ctx.GetParameterInt("level", 1)
if level < 1 {
level = 1
}
if level > 100 {
level = 100
}
level := min(max(ctx.GetParameterInt("level", 1), 1), 100)
spawn.SetLevel(int16(level))
ctx.Debug("Set level to %d for spawn %s", level, spawn.GetName())
@ -289,9 +268,9 @@ func AddSpellBonus(ctx *events.EventContext) error {
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)",
ctx.Debug("Added spell bonus (type: %d, value: %f, spell: %d) to spawn %s (not yet implemented)",
bonusType, value, spellID, spawn.GetName())
return nil
}
@ -306,9 +285,9 @@ func RemoveSpellBonus(ctx *events.EventContext) error {
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)",
ctx.Debug("Removed spell bonus (type: %d, spell: %d) from spawn %s (not yet implemented)",
bonusType, spellID, spawn.GetName())
return nil
}
@ -323,9 +302,9 @@ func AddSkillBonus(ctx *events.EventContext) error {
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)",
ctx.Debug("Added skill bonus (type: %d, value: %f) to spawn %s (not yet implemented)",
skillType, value, spawn.GetName())
return nil
}
@ -339,9 +318,9 @@ func RemoveSkillBonus(ctx *events.EventContext) error {
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)",
ctx.Debug("Removed skill bonus (type: %d) from spawn %s (not yet implemented)",
skillType, spawn.GetName())
return nil
}
@ -417,13 +396,7 @@ func SetTradeskillLevel(ctx *events.EventContext) error {
return fmt.Errorf("no spawn in context")
}
level := ctx.GetParameterInt("level", 1)
if level < 1 {
level = 1
}
if level > 100 {
level = 100
}
level := min(max(ctx.GetParameterInt("level", 1), 1), 100)
// TODO: Implement tradeskill level when available
ctx.Debug("Set tradeskill level to %d for spawn %s (not yet implemented)", level, spawn.GetName())
@ -472,7 +445,7 @@ func SetModelType(ctx *events.EventContext) error {
}
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
@ -498,7 +471,7 @@ func SetDeity(ctx *events.EventContext) error {
}
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
@ -524,8 +497,8 @@ func SetAlignment(ctx *events.EventContext) error {
}
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
}
}

View File

@ -12,11 +12,11 @@ import (
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")
}
@ -30,11 +30,11 @@ func Attack(ctx *events.EventContext) error {
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")
}
@ -75,7 +75,7 @@ func GetMostHated(ctx *events.EventContext) error {
func SetTarget(ctx *events.EventContext) error {
spawn := ctx.GetSpawn()
target := ctx.GetTarget()
if spawn == nil {
return fmt.Errorf("no spawn in context")
}
@ -131,11 +131,11 @@ func SetInCombat(ctx *events.EventContext) error {
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")
}
@ -148,9 +148,9 @@ func SpellDamage(ctx *events.EventContext) error {
}
// TODO: Implement spell damage system with damage types, resistances, etc.
ctx.Debug("Spell damage %f (type %d) from %s to %s (not yet implemented)",
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
}
@ -159,11 +159,11 @@ func SpellDamage(ctx *events.EventContext) error {
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")
}
@ -178,9 +178,9 @@ func SpellDamageExt(ctx *events.EventContext) error {
}
// 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)",
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
}
@ -222,11 +222,11 @@ func DamageSpawn(ctx *events.EventContext) error {
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")
}
@ -239,9 +239,9 @@ func ProcDamage(ctx *events.EventContext) error {
}
// TODO: Implement proc damage system
ctx.Debug("Proc damage %f (type %d) from %s to %s (not yet implemented)",
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
}
@ -250,11 +250,11 @@ func ProcDamage(ctx *events.EventContext) error {
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")
}
@ -270,11 +270,11 @@ func ProcHate(ctx *events.EventContext) error {
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")
}
@ -283,7 +283,7 @@ func Knockback(ctx *events.EventContext) error {
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)",
ctx.Debug("Knockback target %s distance %f with vertical %f from %s (not yet implemented)",
target.GetName(), distance, verticalLift, caster.GetName())
return nil
}
@ -291,7 +291,7 @@ func Knockback(ctx *events.EventContext) error {
// 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")
}
@ -365,11 +365,11 @@ func LastSpellAttackHit(ctx *events.EventContext) error {
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")
}
@ -383,11 +383,11 @@ func IsBehind(ctx *events.EventContext) error {
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")
}
@ -401,11 +401,11 @@ func IsFlanking(ctx *events.EventContext) error {
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")
}
@ -435,7 +435,7 @@ func GetEncounter(ctx *events.EventContext) error {
}
// TODO: Implement encounter retrieval
ctx.SetResult("encounter", []interface{}{}) // Empty list for now
ctx.SetResult("encounter", []any{}) // Empty list for now
return nil
}
@ -447,7 +447,7 @@ func GetHateList(ctx *events.EventContext) error {
}
// TODO: Implement hate list retrieval
ctx.SetResult("hate_list", []interface{}{}) // Empty list for now
ctx.SetResult("hate_list", []any{}) // Empty list for now
return nil
}
@ -503,11 +503,11 @@ func GetRunbackDistance(ctx *events.EventContext) error {
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")
}
@ -557,7 +557,7 @@ func Resurrect(ctx *events.EventContext) error {
// Restore HP and power
maxHP := float64(spawn.GetTotalHP())
maxPower := float64(spawn.GetTotalPower())
newHP := maxHP * (hpPercent / 100.0)
newPower := maxPower * (powerPercent / 100.0)
@ -565,7 +565,7 @@ func Resurrect(ctx *events.EventContext) error {
spawn.SetPower(int32(newPower))
spawn.SetAlive(true)
ctx.Debug("Resurrected spawn %s with %.1f%% HP and %.1f%% power",
ctx.Debug("Resurrected spawn %s with %.1f%% HP and %.1f%% power",
spawn.GetName(), hpPercent, powerPercent)
return nil
}
@ -608,4 +608,4 @@ func SetAttackable(ctx *events.EventContext) error {
// TODO: Implement attackable flag
ctx.Debug("Set attackable to %t for spawn %s (not yet implemented)", attackable, spawn.GetName())
return nil
}
}

View File

@ -34,7 +34,7 @@ func SetMaxHP(ctx *events.EventContext) error {
maxHP := ctx.GetParameterFloat("max_hp", 0)
if maxHP < 0 {
return fmt.Errorf("Max HP cannot be negative")
return fmt.Errorf("max HP cannot be negative")
}
spawn.SetTotalHP(int32(maxHP))
@ -74,7 +74,7 @@ func SetMaxPower(ctx *events.EventContext) error {
maxPower := ctx.GetParameterFloat("max_power", 0)
if maxPower < 0 {
return fmt.Errorf("Max power cannot be negative")
return fmt.Errorf("max power cannot be negative")
}
spawn.SetTotalPower(int32(maxPower))
@ -323,7 +323,7 @@ func SpellHeal(ctx *events.EventContext) error {
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)
@ -362,4 +362,4 @@ func IsAlive(ctx *events.EventContext) error {
ctx.SetResult("is_alive", spawn.IsAlive())
return nil
}
}

View File

@ -57,8 +57,8 @@ type EventContext struct {
Quest *quests.Quest
// Parameters and results
Parameters map[string]interface{}
Results map[string]interface{}
Parameters map[string]any
Results map[string]any
// Synchronization
mutex sync.RWMutex
@ -75,8 +75,8 @@ type EventHandler struct {
// 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{})
Debug(msg string, args ...any)
Info(msg string, args ...any)
Warn(msg string, args ...any)
Error(msg string, args ...any)
}

View File

@ -138,7 +138,7 @@ func createGuildTables(conn *sqlite.Conn) error {
}
// execSQL is a helper to execute SQL with parameters
func execSQL(t *testing.T, pool *sqlitex.Pool, query string, args ...interface{}) {
func execSQL(t *testing.T, pool *sqlitex.Pool, query string, args ...any) {
conn, err := pool.Take(context.Background())
if err != nil {
t.Fatalf("Failed to get connection: %v", err)

View File

@ -36,7 +36,7 @@ func createTestPool(t *testing.T) *sqlitex.Pool {
}
// execSQL is a helper to execute SQL with parameters
func execSQL(t *testing.T, pool *sqlitex.Pool, query string, args ...interface{}) {
func execSQL(t *testing.T, pool *sqlitex.Pool, query string, args ...any) {
conn, err := pool.Take(context.Background())
if err != nil {
t.Fatalf("Failed to get connection: %v", err)
@ -605,8 +605,8 @@ func BenchmarkDatabaseHeroicOPManager_LoadStarters(b *testing.B) {
shift_icon, spell_id, chance, ability1, ability2, ability3, ability4,
ability5, ability6, name, description)
VALUES (?, ?, ?, ?, 0, 0, 0, 0, 0.0, ?, ?, ?, ?, ?, ?, ?, ?)`,
i, HOTypeStarter, i%10+1, i*10,
i*1, i*2, i*3, i*4, i*5, i*6,
i, HOTypeStarter, i%10+1, i*10,
i*1, i*2, i*3, i*4, i*5, i*6,
fmt.Sprintf("Starter %d", i), fmt.Sprintf("Description %d", i))
}
@ -718,4 +718,4 @@ func BenchmarkDatabaseHeroicOPManager_SaveHOInstance(b *testing.B) {
b.Fatalf("Failed to save HO instance: %v", err)
}
}
}
}

View File

@ -20,9 +20,9 @@ type LootSystem struct {
// LootSystemConfig holds configuration for the loot system
type LootSystemConfig struct {
DatabasePool *sqlitex.Pool
DatabasePool *sqlitex.Pool
// @TODO: Fix MasterItemListService type import
ItemMasterList interface{} // was items.MasterItemListService
ItemMasterList any // was items.MasterItemListService
PlayerService PlayerService
ZoneService ZoneService
ClientService ClientService
@ -339,7 +339,7 @@ func (ls *LootSystem) ValidateItemsInLootTables() []ValidationError {
for _, drop := range table.Drops {
// @TODO: Fix MasterItemListService type import - itemMasterList method calls disabled
// item := ls.Manager.itemMasterList.GetItem(drop.ItemID)
var item interface{} = nil
var item any = nil
if item == nil {
errors = append(errors, ValidationError{
Type: "missing_item",
@ -395,7 +395,7 @@ func (ls *LootSystem) GetLootPreview(spawnID int32, context *LootContext) (*Loot
for _, drop := range table.Drops {
// @TODO: Fix MasterItemListService type import - itemMasterList method calls disabled
// item := ls.Manager.itemMasterList.GetItem(drop.ItemID)
var item interface{} = nil
var item any = nil
if item == nil {
continue
}

View File

@ -12,19 +12,19 @@ import (
// LootManager handles all loot generation and management
type LootManager struct {
database *LootDatabase
database *LootDatabase
// @TODO: Fix MasterItemListService type import
itemMasterList interface{} // was items.MasterItemListService
statistics *LootStatistics
itemMasterList any // was items.MasterItemListService
statistics *LootStatistics
treasureChests map[int32]*TreasureChest // chest_id -> TreasureChest
chestIDCounter int32
random *rand.Rand
mutex sync.RWMutex
random *rand.Rand
mutex sync.RWMutex
}
// NewLootManager creates a new loot manager
// @TODO: Fix MasterItemListService type import
func NewLootManager(database *LootDatabase, itemMasterList interface{}) *LootManager {
func NewLootManager(database *LootDatabase, itemMasterList any) *LootManager {
return &LootManager{
database: database,
itemMasterList: itemMasterList,
@ -38,7 +38,7 @@ func NewLootManager(database *LootDatabase, itemMasterList interface{}) *LootMan
// GenerateLoot generates loot for a spawn based on its loot table assignments
func (lm *LootManager) GenerateLoot(spawnID int32, context *LootContext) (*LootResult, error) {
log.Printf("%s Generating loot for spawn %d", LogPrefixGeneration, spawnID)
result := &LootResult{
Items: make([]*items.Item, 0),
Coins: 0,
@ -46,7 +46,7 @@ func (lm *LootManager) GenerateLoot(spawnID int32, context *LootContext) (*LootR
// Get loot tables for this spawn
tableIDs := lm.database.GetSpawnLootTables(spawnID)
// Also check for global loot tables
globalLoot := lm.database.GetGlobalLootTables(context.PlayerLevel, context.PlayerRace, context.ZoneID)
for _, global := range globalLoot {
@ -71,9 +71,9 @@ func (lm *LootManager) GenerateLoot(spawnID int32, context *LootContext) (*LootR
lm.statistics.RecordLoot(tableIDs[0], result) // Use first table for stats
}
log.Printf("%s Generated %d items and %d coins for spawn %d",
log.Printf("%s Generated %d items and %d coins for spawn %d",
LogPrefixGeneration, len(result.Items), result.Coins, spawnID)
return result, nil
}
@ -133,7 +133,7 @@ func (lm *LootManager) processLootTable(tableID int32, context *LootContext, res
// log.Printf("%s Item template %d not found for loot drop", LogPrefixGeneration, drop.ItemID)
// continue
// }
var itemTemplate interface{} = nil
var itemTemplate any = nil
if itemTemplate == nil {
log.Printf("%s Item template %d not found for loot drop (disabled due to type import issue)", LogPrefixGeneration, drop.ItemID)
continue
@ -143,7 +143,7 @@ func (lm *LootManager) processLootTable(tableID int32, context *LootContext, res
// Create item instance
// item := items.NewItemFromTemplate(itemTemplate)
var item *items.Item = nil
// Set charges if specified
if drop.ItemCharges > 0 {
item.Details.Count = drop.ItemCharges
@ -157,8 +157,8 @@ func (lm *LootManager) processLootTable(tableID int32, context *LootContext, res
result.AddItem(item)
itemsGenerated++
log.Printf("%s Generated item %d (%s) from table %d",
log.Printf("%s Generated item %d (%s) from table %d",
LogPrefixGeneration, drop.ItemID, item.Name, tableID)
}
@ -173,7 +173,7 @@ func (lm *LootManager) rollProbability(probability float32) bool {
if probability >= 100.0 {
return true
}
roll := lm.random.Float32() * 100.0
return roll <= probability
}
@ -183,14 +183,14 @@ func (lm *LootManager) generateCoins(minCoin, maxCoin int32) int32 {
if minCoin >= maxCoin {
return minCoin
}
return minCoin + lm.random.Int31n(maxCoin-minCoin+1)
}
// CreateTreasureChest creates a treasure chest for loot
func (lm *LootManager) CreateTreasureChest(spawnID int32, zoneID int32, x, y, z, heading float32,
func (lm *LootManager) CreateTreasureChest(spawnID int32, zoneID int32, x, y, z, heading float32,
lootResult *LootResult, lootRights []uint32) (*TreasureChest, error) {
lm.mutex.Lock()
defer lm.mutex.Unlock()
@ -223,12 +223,12 @@ func (lm *LootManager) CreateTreasureChest(spawnID int32, zoneID int32, x, y, z,
// Store chest
lm.treasureChests[chestID] = chest
// Record statistics
lm.statistics.RecordChest()
log.Printf("%s Created treasure chest %d (%s) at (%.2f, %.2f, %.2f) with %d items and %d coins",
LogPrefixChest, chestID, appearance.Name, x, y, z,
log.Printf("%s Created treasure chest %d (%s) at (%.2f, %.2f, %.2f) with %d items and %d coins",
LogPrefixChest, chestID, appearance.Name, x, y, z,
len(lootResult.GetItems()), lootResult.GetCoins())
return chest, nil
@ -237,13 +237,13 @@ func (lm *LootManager) CreateTreasureChest(spawnID int32, zoneID int32, x, y, z,
// getHighestItemTier finds the highest tier among items
func (lm *LootManager) getHighestItemTier(items []*items.Item) int8 {
var highest int8 = LootTierCommon
for _, item := range items {
if item.Details.Tier > highest {
highest = item.Details.Tier
}
}
return highest
}
@ -251,7 +251,7 @@ func (lm *LootManager) getHighestItemTier(items []*items.Item) int8 {
func (lm *LootManager) GetTreasureChest(chestID int32) *TreasureChest {
lm.mutex.RLock()
defer lm.mutex.RUnlock()
return lm.treasureChests[chestID]
}
@ -259,7 +259,7 @@ func (lm *LootManager) GetTreasureChest(chestID int32) *TreasureChest {
func (lm *LootManager) RemoveTreasureChest(chestID int32) {
lm.mutex.Lock()
defer lm.mutex.Unlock()
delete(lm.treasureChests, chestID)
log.Printf("%s Removed treasure chest %d", LogPrefixChest, chestID)
}
@ -287,10 +287,10 @@ func (lm *LootManager) LootChestItem(chestID int32, playerID uint32, itemUniqueI
chest.LootResult.mutex.Lock()
chest.LootResult.Items = append(chest.LootResult.Items[:i], chest.LootResult.Items[i+1:]...)
chest.LootResult.mutex.Unlock()
log.Printf("%s Player %d looted item %d (%s) from chest %d",
log.Printf("%s Player %d looted item %d (%s) from chest %d",
LogPrefixChest, playerID, item.Details.ItemID, item.Name, chestID)
return item, nil
}
}
@ -323,7 +323,7 @@ func (lm *LootManager) LootChestCoins(chestID int32, playerID uint32) (int32, er
chest.LootResult.Coins = 0
chest.LootResult.mutex.Unlock()
log.Printf("%s Player %d looted %d coins from chest %d",
log.Printf("%s Player %d looted %d coins from chest %d",
LogPrefixChest, playerID, coins, chestID)
return coins, nil
@ -356,7 +356,7 @@ func (lm *LootManager) LootChestAll(chestID int32, playerID uint32) (*LootResult
chest.LootResult.Coins = 0
chest.LootResult.mutex.Unlock()
log.Printf("%s Player %d looted all (%d items, %d coins) from chest %d",
log.Printf("%s Player %d looted all (%d items, %d coins) from chest %d",
LogPrefixChest, playerID, len(result.Items), result.Coins, chestID)
return result, nil
@ -385,12 +385,12 @@ func (lm *LootManager) CleanupExpiredChests() {
for chestID, chest := range lm.treasureChests {
age := now.Sub(chest.Created).Seconds()
// Remove empty chests after ChestDespawnTime
if chest.LootResult.IsEmpty() && age > ChestDespawnTime {
expired = append(expired, chestID)
}
// Force remove all chests after ChestCleanupTime
if age > ChestCleanupTime {
expired = append(expired, chestID)
@ -437,7 +437,7 @@ func (lm *LootManager) GetPlayerChests(playerID uint32) []*TreasureChest {
return chests
}
// GetStatistics returns loot generation statistics
// GetStatistics returns loot generation statistics
func (lm *LootManager) GetStatistics() LootStatistics {
return lm.statistics.GetStatistics()
}
@ -488,6 +488,6 @@ func (lm *LootManager) StartCleanupTimer() {
lm.CleanupExpiredChests()
}
}()
log.Printf("%s Started chest cleanup timer", LogPrefixLoot)
}
}

View File

@ -74,7 +74,7 @@ func (db *SQLiteDatabase) SaveRaceType(modelType int16, raceType *RaceType) erro
`
err = sqlitex.ExecuteTransient(conn, query, &sqlitex.ExecOptions{
Args: []interface{}{modelType, raceType.RaceTypeID, raceType.Category, raceType.Subcategory, raceType.ModelName},
Args: []any{modelType, raceType.RaceTypeID, raceType.Category, raceType.Subcategory, raceType.ModelName},
})
if err != nil {
@ -95,7 +95,7 @@ func (db *SQLiteDatabase) DeleteRaceType(modelType int16) error {
query := `DELETE FROM race_types WHERE model_type = ?`
err = sqlitex.ExecuteTransient(conn, query, &sqlitex.ExecOptions{
Args: []interface{}{modelType},
Args: []any{modelType},
})
if err != nil {
@ -146,4 +146,4 @@ func (db *SQLiteDatabase) CreateRaceTypesTable() error {
}
return nil
}
}

View File

@ -359,7 +359,7 @@ func TestSQLiteDatabaseIndexes(t *testing.T) {
var indexExists bool
query := "SELECT name FROM sqlite_master WHERE type='index' AND name=?"
err = sqlitex.ExecuteTransient(conn, query, &sqlitex.ExecOptions{
Args: []interface{}{indexName},
Args: []any{indexName},
ResultFunc: func(stmt *sqlite.Stmt) error {
indexExists = true
return nil
@ -499,4 +499,4 @@ func BenchmarkSQLiteDatabaseLoad(b *testing.B) {
masterList := NewMasterRaceTypeList()
db.LoadRaceTypes(masterList)
}
}
}

View File

@ -43,7 +43,7 @@ func (p *Player) GetPrimaryStat() int32 {
}
// GetTarget returns the player's current target (stub)
func (p *Player) GetTarget() interface{} {
func (p *Player) GetTarget() any {
// TODO: Implement targeting system
return nil
}
@ -125,7 +125,7 @@ func (p *Player) GetInfoStructCoinCopper() int32 {
return playerCoinBreakdown[p.GetCharacterID()]["copper"]
}
// GetCoinSilver returns silver coins (stub)
// GetCoinSilver returns silver coins (stub)
func (p *Player) GetInfoStructCoinSilver() int32 {
if playerCoinBreakdown[p.GetCharacterID()] == nil {
return 0
@ -526,4 +526,4 @@ func (p *Player) RemoveSkillHelper(skillID int32) {
func GetSpellLinkedTimerID(spellData *spells.SpellData) int32 {
// TODO: Implement LinkedTimerID field access
return 0
}
}

View File

@ -7,18 +7,18 @@ import (
// RecipeManager provides high-level recipe system management with database integration
type RecipeManager struct {
db interface{} // Placeholder for database connection
db any // Placeholder for database connection
masterRecipeList *MasterRecipeList
masterRecipeBookList *MasterRecipeBookList
loadedRecipes map[int32]*Recipe
loadedRecipeBooks map[int32]*Recipe
// Statistics
stats RecipeManagerStats
statisticsEnabled bool
// Thread safety
mu sync.RWMutex
mu sync.RWMutex
}
// RecipeManagerStats contains statistics about recipe system operations
@ -28,7 +28,7 @@ type RecipeManagerStats struct {
LoadOperations int32 `json:"load_operations"`
SaveOperations int32 `json:"save_operations"`
ValidationErrors int32 `json:"validation_errors"`
mu sync.RWMutex
}
@ -68,7 +68,7 @@ func (rm *RecipeManager) GetMasterRecipeBookList() *MasterRecipeBookList {
func (rm *RecipeManager) GetRecipe(recipeID int32) *Recipe {
rm.mu.RLock()
defer rm.mu.RUnlock()
return rm.loadedRecipes[recipeID]
}
@ -76,7 +76,7 @@ func (rm *RecipeManager) GetRecipe(recipeID int32) *Recipe {
func (rm *RecipeManager) GetRecipeBook(bookID int32) *Recipe {
rm.mu.RLock()
defer rm.mu.RUnlock()
return rm.loadedRecipeBooks[bookID]
}
@ -88,7 +88,7 @@ func (rm *RecipeManager) GetStatistics() RecipeManagerStats {
rm.stats.mu.RLock()
defer rm.stats.mu.RUnlock()
// Return a copy without the mutex to avoid copying lock value
return RecipeManagerStats{
TotalRecipesLoaded: rm.stats.TotalRecipesLoaded,
@ -148,6 +148,6 @@ func (rm *RecipeManager) Validate() []string {
func (rm *RecipeManager) Size() (recipes int32, recipeBooks int32) {
rm.mu.RLock()
defer rm.mu.RUnlock()
return int32(len(rm.loadedRecipes)), int32(len(rm.loadedRecipeBooks))
}
}

851
old/LoginServer/client.hpp Normal file
View File

@ -0,0 +1,851 @@
// EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team - GPL License
#pragma once
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <iomanip>
#include <stdlib.h>
#include <assert.h>
#include <string>
#include <vector>
#include <map>
#include "net.hpp"
#include "lworld.hpp"
#include "login_structs.hpp"
#include "login_account.hpp"
#include "login_database.hpp"
#include "../common/tcp.hpp"
#include "../common/timer.hpp"
#include "../common/linked_list.hpp"
#include "../common/log.hpp"
#include "../common/debug.hpp"
#include "../common/config_reader.hpp"
#include "../common/misc_functions.hpp"
#include "../common/stream/eq_stream.hpp"
#include "../common/packet/packet_dump.hpp"
#include "../common/packet/packet_struct.hpp"
#include "../common/packet/packet_functions.hpp"
#include "../common/opcodes/emu_opcodes.hpp"
using namespace std;
enum eLoginMode { None, Normal, Registration };
extern NetConnection net;
extern LWorldList world_list;
extern ClientList client_list;
extern LoginDatabase database;
extern map<int16,OpcodeManager*> EQOpcodeManager;
extern ConfigReader configReader;
class DelayQue
{
public:
// Constructor initializes timer and packet for delayed queue processing
DelayQue(Timer* in_timer, EQApplicationPacket* in_packet)
{
timer = in_timer;
packet = in_packet;
}
Timer* timer; // Timer for delay processing
EQApplicationPacket* packet; // Packet to be processed after delay
};
class Client
{
public:
// Constructor initializes client connection and default values
Client(EQStream* ieqnc)
{
eqnc = ieqnc;
ip = eqnc->GetrIP();
port = ntohs(eqnc->GetrPort());
account_id = 0;
lsadmin = 0;
worldadmin = 0;
lsstatus = 0;
version = 0;
kicked = false;
verified = false;
memset(bannedreason, 0, sizeof(bannedreason));
memset(key,0,10);
LoginMode = None;
num_updates = 0;
updatetimer = new Timer(500);
updatelisttimer = new Timer(10000);
disconnectTimer = 0;
memset(ClientSession,0,25);
request_num = 0;
login_account = 0;
createRequest = 0;
playWaitTimer = NULL;
start = false;
update_position = 0;
update_packets = 0;
needs_world_list = true;
sent_character_list = false;
}
// Destructor cleans up allocated resources and closes connection
~Client()
{
safe_delete(login_account);
eqnc->Close();
safe_delete(playWaitTimer);
safe_delete(createRequest);
safe_delete(disconnectTimer);
safe_delete(updatetimer);
}
// Main processing loop for client connections and packet handling
bool Process()
{
if(!start && !eqnc->CheckActive()) {
if(!playWaitTimer)
playWaitTimer = new Timer(5000);
else if(playWaitTimer->Check()) {
safe_delete(playWaitTimer);
return false;
}
return true;
}
else if(!start) {
safe_delete(playWaitTimer);
start = true;
}
if(disconnectTimer && disconnectTimer->Check()) {
safe_delete(disconnectTimer);
getConnection()->SendDisconnect();
}
if(!kicked) {
EQApplicationPacket *app = 0;
if(playWaitTimer != NULL && playWaitTimer->Check()) {
SendPlayFailed(PLAY_ERROR_SERVER_TIMEOUT);
safe_delete(playWaitTimer);
}
if(!needs_world_list && updatetimer && updatetimer->Check()) {
if(updatelisttimer && updatelisttimer->Check()) {
if(num_updates >= 180) { // 30 minutes
getConnection()->SendDisconnect();
}
else {
vector<PacketStruct*>::iterator itr;
if(update_packets) {
for(itr = update_packets->begin(); itr != update_packets->end(); itr++) {
safe_delete(*itr);
}
}
safe_delete(update_packets);
update_packets = world_list.GetServerListUpdate(version);
}
num_updates++;
}
else {
if(!update_packets) {
update_packets = world_list.GetServerListUpdate(version);
}
else {
if(update_position < update_packets->size()) {
QueuePacket(update_packets->at(update_position)->serialize());
update_position++;
}
else
update_position = 0;
}
}
}
while(app = eqnc->PopPacket()) {
switch(app->GetOpcode()) {
case OP_LoginRequestMsg:
HandleLoginRequest(app);
break;
case OP_KeymapLoadMsg:
// Keymap message - currently unused
break;
case OP_AllWSDescRequestMsg:
HandleWorldServerListRequest();
break;
case OP_LsClientCrashlogReplyMsg:
SaveErrorsToDB(app, "Crash Log", GetVersion());
break;
case OP_LsClientVerifylogReplyMsg:
SaveErrorsToDB(app, "Verify Log", GetVersion());
break;
case OP_LsClientAlertlogReplyMsg:
SaveErrorsToDB(app, "Alert Log", GetVersion());
break;
case OP_LsClientBaselogReplyMsg:
SaveErrorsToDB(app, "Base Log", GetVersion());
break;
case OP_AllCharactersDescRequestMsg:
break;
case OP_CreateCharacterRequestMsg:
HandleCreateCharacterRequest(app);
break;
case OP_PlayCharacterRequestMsg:
HandlePlayCharacterRequest(app);
break;
case OP_DeleteCharacterRequestMsg:
HandleDeleteCharacterRequest(app);
break;
default:
HandleUnknownOpcode(app);
}
delete app;
}
}
if(!eqnc->CheckActive()) {
return false;
}
return true;
}
// Handles login request packets and validates credentials
void HandleLoginRequest(EQApplicationPacket* app)
{
DumpPacket(app);
PacketStruct* packet = configReader.getStruct("LS_LoginRequest", 1);
if(packet && packet->LoadPacketData(app->pBuffer,app->size)) {
version = packet->getType_int16_ByName("version");
LogWrite(LOGIN__DEBUG, 0, "Login", "Classic Client Version Provided: %i", version);
if(version == 0 || EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) {
safe_delete(packet);
packet = configReader.getStruct("LS_LoginRequest", 1208);
if(packet && packet->LoadPacketData(app->pBuffer, app->size)) {
version = packet->getType_int16_ByName("version");
}
else
return;
}
LogWrite(LOGIN__DEBUG, 0, "Login", "New Client Version Provided: %i", version);
if(EQOpcodeManager.count(GetOpcodeVersion(version)) == 0) {
LogWrite(LOGIN__ERROR, 0, "Login", "Incompatible client version provided: %i", version);
SendLoginDenied();
safe_delete(packet);
return;
}
if(EQOpcodeManager.count(GetOpcodeVersion(version)) > 0 && getConnection()) {
getConnection()->SetClientVersion(GetVersion());
EQ2_16BitString username = packet->getType_EQ2_16BitString_ByName("username");
EQ2_16BitString password = packet->getType_EQ2_16BitString_ByName("password");
LoginAccount* acct = database.LoadAccount(username.data.c_str(),password.data.c_str(), net.IsAllowingAccountCreation());
if(acct) {
Client* otherclient = client_list.FindByLSID(acct->getLoginAccountID());
if(otherclient)
otherclient->getConnection()->SendDisconnect(); // Prevent double login
}
if(acct) {
SetAccountName(username.data.c_str());
database.UpdateAccountIPAddress(acct->getLoginAccountID(), getConnection()->GetrIP());
database.UpdateAccountClientDataVersion(acct->getLoginAccountID(), version);
LogWrite(LOGIN__INFO, 0, "Login", "%s successfully logged in.", (char*)username.data.c_str());
}
else {
if(username.size > 0)
LogWrite(LOGIN__ERROR, 0, "Login", "%s login failed!", (char*)username.data.c_str());
else
LogWrite(LOGIN__ERROR, 0, "Login", "[UNKNOWN USER] login failed!");
}
if(!acct)
SendLoginDenied();
else {
needs_world_list = true;
SetLoginAccount(acct);
SendLoginAccepted();
}
}
else {
cout << "Error bad version: " << version << endl;
SendLoginDeniedBadVersion();
}
}
else {
cout << "Error loading LS_LoginRequest packet: \n";
}
safe_delete(packet);
}
// Handles world server list request and sends character list
void HandleWorldServerListRequest()
{
SendWorldList();
needs_world_list = false;
if(!sent_character_list) {
database.LoadCharacters(GetLoginAccount(), GetVersion());
sent_character_list = true;
}
SendCharList();
}
// Handles character creation requests from clients
void HandleCreateCharacterRequest(EQApplicationPacket* app)
{
PacketStruct* packet = configReader.getStruct("CreateCharacter", GetVersion());
DumpPacket(app);
playWaitTimer = new Timer(15000);
playWaitTimer->Start();
LogWrite(WORLD__INFO, 1, "World", "Character creation request from account %s", GetAccountName());
if(packet->LoadPacketData(app->pBuffer,app->size, GetVersion() <= 561 ? false : true)) {
DumpPacket(app->pBuffer, app->size);
packet->setDataByName("account_id",GetAccountID());
LWorld* world_server = world_list.FindByID(packet->getType_int32_ByName("server_id"));
if(!world_server) {
DumpPacket(app->pBuffer, app->size);
cout << GetAccountName() << " attempted creation of character with an invalid server id of: " << packet->getType_int32_ByName("server_id") << "\n";
}
else {
createRequest = packet;
ServerPacket* outpack = new ServerPacket(ServerOP_CharacterCreate, app->size+sizeof(int16));
int16 out_version = GetVersion();
memcpy(outpack->pBuffer, &out_version, sizeof(int16));
memcpy(outpack->pBuffer + sizeof(int16), app->pBuffer, app->size);
uchar* tmp = outpack->pBuffer;
if(out_version<=283)
tmp+=2;
else if(out_version == 373) {
tmp += 6;
}
else
tmp += 7;
int32 account_id = GetAccountID();
memcpy(tmp, &account_id, sizeof(int32));
world_server->SendPacket(outpack);
safe_delete(outpack);
}
}
else {
LogWrite(WORLD__ERROR, 1, "World", "Error in character creation request from account %s!", GetAccountName());
safe_delete(packet);
}
}
// Handles character play requests and forwards to world server
void HandlePlayCharacterRequest(EQApplicationPacket* app)
{
int32 char_id = 0;
int32 server_id = 0;
PacketStruct* request = configReader.getStruct("LS_PlayRequest",GetVersion());
if(request && request->LoadPacketData(app->pBuffer,app->size)) {
char_id = request->getType_int32_ByName("char_id");
if(GetVersion() <= 283) {
server_id = database.GetServer(GetAccountID(), char_id, request->getType_EQ2_16BitString_ByName("name").data);
}
else {
server_id = request->getType_int32_ByName("server_id");
}
LWorld* world = world_list.FindByID(server_id);
string name = database.GetCharacterName(char_id,server_id,GetAccountID());
if(world && name.length() > 0) {
pending_play_char_id = char_id;
ServerPacket* outpack = new ServerPacket(ServerOP_UsertoWorldReq, sizeof(UsertoWorldRequest_Struct));
UsertoWorldRequest_Struct* req = (UsertoWorldRequest_Struct*)outpack->pBuffer;
req->char_id = char_id;
req->lsaccountid = GetAccountID();
req->worldid = server_id;
struct in_addr in;
in.s_addr = GetIP();
strcpy(req->ip_address, inet_ntoa(in));
world->SendPacket(outpack);
delete outpack;
safe_delete(playWaitTimer);
playWaitTimer = new Timer(5000);
playWaitTimer->Start();
}
else {
cout << GetAccountName() << " sent invalid Play Request: \n";
SendPlayFailed(PLAY_ERROR_PROBLEM);
DumpPacket(app);
}
}
safe_delete(request);
}
// Handles character deletion requests
void HandleDeleteCharacterRequest(EQApplicationPacket* app)
{
PacketStruct* request = configReader.getStruct("LS_DeleteCharacterRequest", GetVersion());
PacketStruct* response = configReader.getStruct("LS_DeleteCharacterResponse", GetVersion());
if(request && response && request->LoadPacketData(app->pBuffer,app->size)) {
EQ2_16BitString name = request->getType_EQ2_16BitString_ByName("name");
int32 acct_id = GetAccountID();
int32 char_id = request->getType_int32_ByName("char_id");
int32 server_id = request->getType_int32_ByName("server_id");
if(database.VerifyDelete(acct_id, char_id, name.data.c_str())) {
response->setDataByName("response", 1);
GetLoginAccount()->removeCharacter((char*)name.data.c_str(), GetVersion());
LWorld* world_server = world_list.FindByID(server_id);
if(world_server != NULL)
world_server->SendDeleteCharacter(char_id, acct_id);
}
else
response->setDataByName("response", 0);
response->setDataByName("server_id", server_id);
response->setDataByName("char_id", char_id);
response->setDataByName("account_id", account_id);
response->setMediumStringByName("name", (char*)name.data.c_str());
response->setDataByName("max_characters", 10);
EQ2Packet* outapp = response->serialize();
QueuePacket(outapp);
this->SendCharList();
}
safe_delete(request);
safe_delete(response);
}
// Handles unknown opcodes and logs them for debugging
void HandleUnknownOpcode(EQApplicationPacket* app)
{
const char* name = app->GetOpcodeName();
if(name)
LogWrite(OPCODE__DEBUG, 1, "Opcode", "%s Received %04X (%i)", name, app->GetRawOpcode(), app->GetRawOpcode());
else
LogWrite(OPCODE__DEBUG, 1, "Opcode", "Received %04X (%i)", app->GetRawOpcode(), app->GetRawOpcode());
}
// Saves client error logs to database after decompression
void SaveErrorsToDB(EQApplicationPacket* app, char* type, int32 version)
{
int32 size = 0;
z_stream zstream;
if(version >= 546) {
memcpy(&size, app->pBuffer + sizeof(int32), sizeof(int32));
zstream.next_in = app->pBuffer + 8;
zstream.avail_in = app->size - 8;
}
else { // box set
size = 0xFFFF;
zstream.next_in = app->pBuffer + 2;
zstream.avail_in = app->size - 2;
}
size++;
char* message = new char[size];
memset(message, 0, size);
int zerror = 0;
zstream.next_out = (BYTE*)message;
zstream.avail_out = size;
zstream.zalloc = Z_NULL;
zstream.zfree = Z_NULL;
zstream.opaque = Z_NULL;
zerror = inflateInit(&zstream);
if(zerror != Z_OK) {
safe_delete_array(message);
return;
}
zerror = inflate(&zstream, 0);
if(message && strlen(message) > 0)
database.SaveClientLog(type, message, GetLoginAccount()->getLoginName(), GetVersion());
safe_delete_array(message);
}
// Handles character creation approval from world server
void CharacterApproved(int32 server_id,int32 char_id)
{
if(createRequest && server_id == createRequest->getType_int32_ByName("server_id")) {
LWorld* world_server = world_list.FindByID(server_id);
if(!world_server)
return;
PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply", GetVersion());
if(packet) {
packet->setDataByName("account_id", GetAccountID());
packet->setDataByName("unknown", 0xFFFFFFFF);
packet->setDataByName("response", CREATESUCCESS_REPLY);
packet->setMediumStringByName("name", (char*)createRequest->getType_EQ2_16BitString_ByName("name").data.c_str());
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
safe_delete(packet);
database.SaveCharacter(createRequest, GetLoginAccount(),char_id, GetVersion());
// refresh characters for this account
database.LoadCharacters(GetLoginAccount(), GetVersion());
SendCharList();
if(GetVersion() <= 561) {
pending_play_char_id = char_id;
ServerPacket* outpack = new ServerPacket(ServerOP_UsertoWorldReq, sizeof(UsertoWorldRequest_Struct));
UsertoWorldRequest_Struct* req = (UsertoWorldRequest_Struct*)outpack->pBuffer;
req->char_id = char_id;
req->lsaccountid = GetAccountID();
req->worldid = server_id;
struct in_addr in;
in.s_addr = GetIP();
strcpy(req->ip_address, inet_ntoa(in));
world_server->SendPacket(outpack);
delete outpack;
}
}
}
else {
cout << GetAccountName() << " received invalid CharacterApproval from server: " << server_id << endl;
}
safe_delete(createRequest);
}
// Handles character creation rejection from world server
void CharacterRejected(int8 reason_number)
{
PacketStruct* packet = configReader.getStruct("LS_CreateCharacterReply", GetVersion());
if(createRequest && packet) {
packet->setDataByName("account_id", GetAccountID());
int8 clientReasonNum = reason_number;
packet->setDataByName("response", clientReasonNum);
packet->setMediumStringByName("name", "");
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
safe_delete(packet);
}
}
// Sends character list to client
void SendCharList()
{
LogWrite(LOGIN__INFO, 0, "Login", "[%s] sending character list.", GetAccountName());
LS_CharSelectList list;
list.loadData(GetAccountID(), GetLoginAccount()->charlist, GetVersion());
EQ2Packet* outapp = list.serialize(GetVersion());
DumpPacket(outapp->pBuffer, outapp->size);
QueuePacket(outapp);
}
// Sends login denied response with bad version error
void SendLoginDeniedBadVersion()
{
EQ2Packet* app = new EQ2Packet(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse));
LS_LoginResponse* ls_response = (LS_LoginResponse*)app->pBuffer;
ls_response->reply_code = 6;
ls_response->unknown03 = 0xFFFFFFFF;
ls_response->unknown04 = 0xFFFFFFFF;
QueuePacket(app);
StartDisconnectTimer();
}
// Sends login denied response for invalid credentials
void SendLoginDenied()
{
EQ2Packet* app = new EQ2Packet(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse));
LS_LoginResponse* ls_response = (LS_LoginResponse*)app->pBuffer;
ls_response->reply_code = 1;
ls_response->unknown03 = 0xFFFFFFFF;
ls_response->unknown04 = 0xFFFFFFFF;
QueuePacket(app);
StartDisconnectTimer();
}
// Sends login accepted response with account details
void SendLoginAccepted(int32 account_id = 1, int8 login_response = 0)
{
PacketStruct* packet = configReader.getStruct("LS_LoginReplyMsg", GetVersion());
if(packet) {
packet->setDataByName("account_id", account_id);
packet->setDataByName("login_response", login_response);
packet->setDataByName("do_not_force_soga", 1);
packet->setDataByName("sub_level", net.GetDefaultSubscriptionLevel());
packet->setDataByName("race_flag", 0x1FFFFF);
packet->setDataByName("class_flag", 0x7FFFFFE);
packet->setMediumStringByName("username", GetAccountName());
packet->setMediumStringByName("password", GetAccountName());
packet->setDataByName("unknown5", net.GetExpansionFlag());
packet->setDataByName("unknown6", 0xFF);
packet->setDataByName("unknown6", 0xFF, 1);
packet->setDataByName("unknown6", 0xFF, 2);
packet->setDataByName("unknown10", 0xFF);
packet->setDataByName("unknown7", net.GetEnabledRaces());
packet->setDataByName("unknown7a", 0xEE);
packet->setDataByName("unknown8", net.GetCitiesFlag(), 1);
EQ2Packet* outapp = packet->serialize();
QueuePacket(outapp);
safe_delete(packet);
}
}
// Sends world server list to client
void SendWorldList()
{
EQ2Packet* pack = world_list.MakeServerListPacket(lsadmin, version);
EQ2Packet* dupe = pack->Copy();
DumpPacket(dupe->pBuffer,dupe->size);
QueuePacket(dupe);
SendLoginAccepted(0, 10); // triggers a different code path in the client to set certain flags
}
// Queues packet for transmission to client
void QueuePacket(EQ2Packet* app)
{
eqnc->EQ2QueuePacket(app);
}
// Handles world server response for character play requests
void WorldResponse(int32 worldid, int8 response, char* ip_address, int32 port, int32 access_key)
{
LWorld* world = world_list.FindByID(worldid);
if(world == 0) {
FatalError(0);
return;
}
if(response != 1) {
if(response == PLAY_ERROR_CHAR_NOT_LOADED) {
string pending_play_char_name = database.GetCharacterName(pending_play_char_id, worldid, GetAccountID());
if(database.VerifyDelete(GetAccountID(), pending_play_char_id, pending_play_char_name.c_str())) {
GetLoginAccount()->removeCharacter((char*)pending_play_char_name.c_str(), GetVersion());
}
}
FatalError(response);
return;
}
PacketStruct* response_packet = configReader.getStruct("LS_PlayResponse", GetVersion());
if(response_packet) {
safe_delete(playWaitTimer);
response_packet->setDataByName("response", 1);
response_packet->setSmallStringByName("server", ip_address);
response_packet->setDataByName("port", port);
response_packet->setDataByName("account_id", GetAccountID());
response_packet->setDataByName("access_code", access_key);
EQ2Packet* outapp = response_packet->serialize();
QueuePacket(outapp);
safe_delete(response_packet);
}
}
// Handles fatal errors and sends play failed response
void FatalError(int8 response)
{
safe_delete(playWaitTimer);
SendPlayFailed(response);
}
// Sends play failed response to client
void SendPlayFailed(int8 response)
{
PacketStruct* response_packet = configReader.getStruct("LS_PlayResponse", GetVersion());
if(response_packet) {
response_packet->setDataByName("response", response);
response_packet->setSmallStringByName("server", "");
response_packet->setDataByName("port", 0);
response_packet->setDataByName("account_id", GetAccountID());
response_packet->setDataByName("access_code", 0);
EQ2Packet* outapp = response_packet->serialize();
QueuePacket(outapp);
safe_delete(response_packet);
}
}
// Starts timer for delayed client disconnection
void StartDisconnectTimer()
{
if(!disconnectTimer) {
disconnectTimer = new Timer(1000);
disconnectTimer->Start();
}
}
// Accessor methods
EQStream* getConnection() { return eqnc; }
LoginAccount* GetLoginAccount() { return login_account; }
void SetLoginAccount(LoginAccount* in_account) {
login_account = in_account;
if(in_account)
account_id = in_account->getLoginAccountID();
}
int16 GetVersion() { return version; }
char* GetKey() { return key; }
void SetKey(char* in_key) { strcpy(key,in_key); }
int32 GetIP() { return ip; }
int16 GetPort() { return port; }
int32 GetAccountID() { return account_id; }
const char* GetAccountName() { return (char*)account_name.c_str(); }
void SetAccountName(const char* name) { account_name = string(name); }
bool AwaitingCharCreationRequest() {
if(createRequest)
return true;
else
return false;
}
// Public member variables
int8 LoginKey[10]; // Login encryption key
int8 ClientSession[25]; // Client session identifier
Timer* updatetimer; // Timer for client updates
Timer* updatelisttimer; // Timer for update list processing
Timer* disconnectTimer; // Timer for delayed disconnection
int16 packettotal; // Total packet count
int32 requested_server_id; // Server ID requested by client
int32 request_num; // Request number counter
LinkedList<DelayQue*> delay_que; // Queue for delayed packet processing
private:
string pending_play_char_name; // Character name for pending play request
int32 pending_play_char_id; // Character ID for pending play request
int8 update_position; // Position in update packet list
int16 num_updates; // Number of updates sent
vector<PacketStruct*>* update_packets; // List of update packets
LoginAccount* login_account; // Associated login account
EQStream* eqnc; // Network connection stream
int32 ip; // Client IP address
int16 port; // Client port number
int32 account_id; // Account ID
string account_name; // Account name
char key[10]; // Client authentication key
int8 lsadmin; // Login server admin level
sint16 worldadmin; // World server admin level
int lsstatus; // Login server status
bool kicked; // Whether client has been kicked
bool verified; // Whether client is verified
bool start; // Whether client has started
bool needs_world_list; // Whether client needs world list
int16 version; // Client version
char bannedreason[30]; // Reason for ban if applicable
bool sent_character_list; // Whether character list has been sent
eLoginMode LoginMode; // Current login mode
PacketStruct* createRequest; // Pending character creation request
Timer* playWaitTimer; // Timer for play request timeout
};
class ClientList
{
public:
// Constructor and destructor for client list management
ClientList() {}
~ClientList() {}
// Adds a new client to the list
void Add(Client* client)
{
MClientList.writelock();
client_list[client] = true;
MClientList.releasewritelock();
}
// Finds client by IP address and port
Client* Get(int32 ip, int16 port)
{
Client* ret = 0;
map<Client*, bool>::iterator itr;
MClientList.readlock();
for(itr = client_list.begin(); itr != client_list.end(); itr++) {
if(itr->first->GetIP() == ip && itr->first->GetPort() == port) {
ret = itr->first;
break;
}
}
MClientList.releasereadlock();
return ret;
}
// Finds clients waiting for character creation and handles rejections
void FindByCreateRequest()
{
Client* client = 0;
map<Client*, bool>::iterator itr;
MClientList.readlock();
for(itr = client_list.begin(); itr != client_list.end(); itr++) {
if(itr->first->AwaitingCharCreationRequest()) {
if(!client)
client = itr->first;
else {
client = 0; // more than 1 character waiting, dont want to send rejection to wrong one
break;
}
}
}
MClientList.releasereadlock();
if(client)
client->CharacterRejected(UNKNOWNERROR_REPLY);
}
// Finds client by login server account ID
Client* FindByLSID(int32 lsaccountid)
{
Client* client = 0;
map<Client*, bool>::iterator itr;
MClientList.readlock();
for(itr = client_list.begin(); itr != client_list.end(); itr++) {
if(itr->first->GetAccountID() == lsaccountid) {
client = itr->first;
break;
}
}
MClientList.releasereadlock();
return client;
}
// Sends packet to all connected clients
void SendPacketToAllClients(EQ2Packet* app)
{
Client* client = 0;
map<Client*, bool>::iterator itr;
MClientList.readlock();
if(client_list.size() > 0) {
for(itr = client_list.begin(); itr != client_list.end(); itr++) {
itr->first->QueuePacket(app->Copy());
}
}
safe_delete(app);
MClientList.releasereadlock();
}
// Processes all clients and removes inactive ones
void Process()
{
Client* client = 0;
vector<Client*> erase_list;
map<Client*, bool>::iterator itr;
MClientList.readlock();
for(itr = client_list.begin(); itr != client_list.end(); itr++) {
client = itr->first;
if(!client->Process())
erase_list.push_back(client);
}
MClientList.releasereadlock();
if(erase_list.size() > 0) {
vector<Client*>::iterator erase_itr;
MClientList.writelock();
for(erase_itr = erase_list.begin(); erase_itr != erase_list.end(); erase_itr++) {
client = *erase_itr;
struct in_addr in;
in.s_addr = client->getConnection()->GetRemoteIP();
net.numclients--;
LogWrite(LOGIN__INFO, 0, "Login", "Removing client from ip: %s on port %i, Account Name: %s", inet_ntoa(in), ntohs(client->getConnection()->GetRemotePort()), client->GetAccountName());
client->getConnection()->Close();
net.UpdateWindowTitle();
client_list.erase(client);
}
MClientList.releasewritelock();
}
}
private:
Mutex MClientList; // Mutex for thread-safe client list access
map<Client*, bool> client_list; // Map of active clients
};

View File

@ -0,0 +1,164 @@
// EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) - GPL License
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <algorithm>
#include <cstring>
#include "packet_headers.hpp"
#include "../common/linked_list.hpp"
#include "../common/packet/packet_struct.hpp"
using namespace std;
/**
* LoginAccount class manages user account data and associated character profiles
* Handles authentication, character management, and account operations
*/
class LoginAccount
{
public:
// Default constructor - initializes account with default values
LoginAccount();
// Parameterized constructor - creates account with specified ID, name, and password
LoginAccount(int32 id, const char* in_name, const char* in_pass);
// Destructor - cleans up all character profiles and allocated memory
~LoginAccount();
// Saves account data to persistent storage
bool SaveAccount(LoginAccount* acct);
// Vector containing all character profiles associated with this account
vector<CharSelectProfile*> charlist;
// Sets the account name from C-string input
void setName(const char* in_name) { strcpy(name, in_name); }
// Sets the account password from C-string input
void setPassword(const char* in_pass) { strcpy(password, in_pass); }
// Sets the authentication status for this account
void setAuthenticated(bool in_auth) { authenticated = in_auth; }
// Sets the unique account identifier
void setAccountID(int32 id) { account_id = id; }
// Adds a character profile to the account's character list
void addCharacter(CharSelectProfile* profile)
{
charlist.push_back(profile);
}
// Removes character by PacketStruct profile reference
void removeCharacter(PacketStruct* profile);
// Removes character by name with version-specific handling
void removeCharacter(char* name, int16 version);
// Serializes character data into provided buffer for network transmission
void serializeCharacter(uchar* buffer, CharSelectProfile* profile);
// Removes and deallocates all character profiles from the account
void flushCharacters();
// Retrieves character profile by name, returns nullptr if not found
CharSelectProfile* getCharacter(char* name);
// Returns the unique account identifier
int32 getLoginAccountID() { return account_id; }
// Returns pointer to account name string
char* getLoginName() { return name; }
// Returns pointer to account password string
char* getLoginPassword() { return password; }
// Returns current authentication status
bool getLoginAuthenticated() { return authenticated; }
private:
int32 account_id; // Unique identifier for this account
char name[32]; // Account name (limited to 31 characters + null terminator)
char password[32]; // Account password (limited to 31 characters + null terminator)
bool authenticated; // Current authentication state
};
// Default constructor implementation - initializes account with default values
inline LoginAccount::LoginAccount()
{
account_id = 0;
memset(name, 0, sizeof(name));
memset(password, 0, sizeof(password));
authenticated = false;
}
// Parameterized constructor implementation - sets up account with provided credentials
inline LoginAccount::LoginAccount(int32 id, const char* in_name, const char* in_pass)
{
account_id = id;
strcpy(name, in_name);
strcpy(password, in_pass);
authenticated = false;
}
// Destructor implementation - properly cleans up all character profiles
inline LoginAccount::~LoginAccount()
{
for(auto iter = charlist.begin(); iter != charlist.end(); iter++) {
delete(*iter); // Clean up character profile memory
}
}
// Removes and deallocates all character profiles from memory
inline void LoginAccount::flushCharacters()
{
for(auto iter = charlist.begin(); iter != charlist.end(); iter++) {
delete(*iter); // Clean up each character profile
}
charlist.clear(); // Clear the vector
}
// Searches for and returns character profile by name
inline CharSelectProfile* LoginAccount::getCharacter(char* name)
{
CharSelectProfile* profile = nullptr;
EQ2_16BitString temp;
// Iterate through character list to find matching name
for(auto char_iterator = charlist.begin(); char_iterator != charlist.end(); char_iterator++) {
profile = *char_iterator;
temp = profile->packet->getType_EQ2_16BitString_ByName("name");
if(strcmp(temp.data.c_str(), name) == 0)
return profile; // Return matching profile
}
return nullptr; // Character not found
}
// Removes character from account by name with client version handling
inline void LoginAccount::removeCharacter(char* name, int16 version)
{
CharSelectProfile* profile = nullptr;
EQ2_16BitString temp;
// Search for character with matching name
for(auto iter = charlist.begin(); iter != charlist.end(); iter++) {
profile = *iter;
temp = profile->packet->getType_EQ2_16BitString_ByName("name");
if(strcmp(temp.data.c_str(), name) == 0) {
if(version <= 561) {
profile->deleted = true; // Workaround for character select crash on legacy clients
}
else {
delete(*iter); // Clean up memory
charlist.erase(iter); // Remove from vector
}
return;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,53 @@
// Copyright (C) 2007 EQ2EMulator Development Team, GPL License
#pragma once
// Core login and session management opcodes
#define OP_Login2 0x0200 // Primary login authentication
#define OP_GetLoginInfo 0x0300 // Request login information from client
#define OP_LoginInfo 0x0100 // Response with login details
#define OP_SessionId 0x0900 // Session identifier assignment
#define OP_SessionKey 0x4700 // Session key exchange
#define OP_Disconnect 0x0500 // Connection termination
#define OP_AllFinish 0x0500 // Process completion acknowledgment
#define OP_Ack5 0x1500 // Generic acknowledgment packet
// Server list and status management
#define OP_SendServersFragment 0x0D00 // Fragment of server list data
#define OP_ServerList 0x4600 // Complete server list
#define OP_RequestServerStatus 0x4800 // Request current server status
#define OP_SendServerStatus 0x4A00 // Response with server status
#define OP_Version 0x5900 // Client/server version verification
// Chat system opcodes
#define OP_Chat_ChannelList 0x0600 // Available chat channels
#define OP_Chat_JoinChannel 0x0700 // Join a chat channel
#define OP_Chat_PartChannel 0x0800 // Leave a chat channel
#define OP_Chat_ChannelMessage 0x0930 // Message to channel
#define OP_Chat_Tell 0x0a00 // Private message
#define OP_Chat_SysMsg 0x0b00 // System message
#define OP_Chat_CreateChannel 0x0c00 // Create new chat channel
#define OP_Chat_ChangeChannel 0x0d00 // Modify channel settings
#define OP_Chat_DeleteChannel 0x0e00 // Remove chat channel
#define OP_Chat_UserList 0x1000 // Users in channel
#define OP_Chat_ChannelWelcome 0x2400 // Welcome message for channel
#define OP_Chat_PopupMakeWindow 0x3000 // Create popup chat window
#define OP_Chat_GuildsList 0x5500 // Available guilds list
#define OP_Chat_GuildEdit 0x5700 // Guild modification
// Account registration and management
#define OP_RegisterAccount 0x2300 // New account creation
#define OP_Reg_GetPricing 0x1a00 // Get pricing for new account signup
//#define OP_Reg_SendPricing 0x0400 // Legacy pricing send (unused)
#define OP_Reg_SendPricing 0x1b00 // Send pricing information
#define OP_Reg_GetPricing2 0x4400 // Get pricing for re-registration
#define OP_Reg_ChangeAcctLogin 0x5100 // Change account login credentials
#define OP_ChangePassword 0x4500 // Password modification
#define OP_LoginBanner 0x5200 // Login banner display
// Billing and subscription management
#define OP_BillingInfoAccepted 0x3300 // Billing information accepted
#define OP_CheckGameCardValid 0x3400 // Validate game card
#define OP_GameCardTimeLeft 0x3600 // Remaining game card time
#define OP_AccountExpired 0x4200 // Account expiration notification
#define OP_RenewAccountBillingInfo 0x7a00 // Billing renewal information

View File

@ -0,0 +1,68 @@
// Copyright (C) 2007 EQ2EMulator Development Team - GPL License
#pragma once
#include "packet_headers.hpp"
#include "../common/types.hpp"
#pragma pack(1)
// Login request packet structure containing user credentials and access information
struct LS_LoginRequest
{
EQ2_16BitString AccessCode; // Access code for login validation
EQ2_16BitString unknown1; // Unknown field - possibly session data
EQ2_16BitString username; // Player username
EQ2_16BitString password; // Player password
EQ2_16BitString unknown2[4]; // Unknown array - possibly additional auth data
int16 unknown3; // Unknown 16-bit field
int32 unknown4[2]; // Unknown 32-bit array
};
// World server status change notification structure
struct LS_WorldStatusChanged
{
int32 server_id; // Unique identifier for the world server
int8 up; // Server online status (1 = up, 0 = down)
int8 locked; // Server locked status (1 = locked, 0 = unlocked)
int8 hidden; // Server visibility status (1 = hidden, 0 = visible)
};
// Character play request for newer client versions
struct LS_PlayCharacterRequest
{
int32 character_id; // Unique character identifier
int32 server_id; // Target server identifier
int16 unknown1; // Unknown field - possibly additional flags
};
// Character play request for older client versions
struct LS_OLDPlayCharacterRequest
{
int32 character_id; // Unique character identifier
EQ2_16BitString name; // Character name string
};
// Account information structure for early client versions
struct LS_CharListAccountInfoEarlyClient
{
int32 account_id; // Unique account identifier
int32 unknown1; // Unknown field - possibly subscription data
int16 unknown2; // Unknown 16-bit field
int32 maxchars; // Maximum characters allowed on account
int8 unknown4; // Unknown field - padding or flags (15 bytes total)
};
// Complete account information structure for character list
struct LS_CharListAccountInfo
{
int32 account_id; // Unique account identifier
int32 unknown1; // Unknown field - possibly subscription type
int16 unknown2; // Unknown 16-bit field
int32 maxchars; // Maximum characters allowed on account
int8 unknown4; // Unknown field - DoF compatibility marker
int32 unknown5[4]; // Unknown array - extended account data
int8 vet_adv_bonus; // Veteran adventure bonus flag (enables 200% bonus)
int8 vet_trade_bonus; // Veteran tradeskill bonus flag (enables free lvl 90 upgrade)
}; // Total structure size: 33 bytes
#pragma pack()

1776
old/LoginServer/lworld.hpp Normal file

File diff suppressed because it is too large Load Diff

112
old/LoginServer/main.cpp Normal file
View File

@ -0,0 +1,112 @@
// Copyright (C) 2007-2021 EQ2Emulator Development Team, GPL v3 License
#include <iostream>
#include <cstdlib>
#include "net.hpp"
EQStreamFactory eqsf(LoginStream);
std::map<int16,OpcodeManager*> EQOpcodeManager;
NetConnection net;
ClientList client_list;
LWorldList world_list;
LoginDatabase database;
ConfigReader configReader;
std::map<int16, int16> EQOpcodeVersions;
Timer statTimer(60000);
volatile bool RunLoops = true;
void CatchSignal(int sig_num)
{
std::cout << "Got signal " << sig_num << std::endl;
RunLoops = false;
}
int main(int argc, char** argv)
{
if (signal(SIGINT, CatchSignal) == SIG_ERR) {
std::cerr << "Could not set signal handler" << std::endl;
}
LogStart();
LogParseConfigs();
net.WelcomeHeader();
srand(time(NULL));
if (!net.ReadLoginConfig())
return 1;
net.InitWebServer(net.GetWebLoginAddress(), net.GetWebLoginPort(), net.GetWebCertFile(), net.GetWebKeyFile(), net.GetWebKeyPassword(), net.GetWebHardcodeUser(), net.GetWebHardcodePassword());
const char* structList[] = { "CommonStructs.xml", "LoginStructs.xml" };
for (int s = 0; s < sizeof(structList) / sizeof(const char*); s++) {
LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s..", structList[s]);
if (configReader.processXML_Elements(structList[s]))
LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s completed..", structList[s]);
else {
LogWrite(INIT__ERROR, 0, "Init", "Loading Structs File %s FAILED!", structList[s]);
return 1;
}
}
LogWrite(INIT__INFO, 0, "Init", "Initialize World List..");
world_list.Init();
if (eqsf.listen_ip_address)
LogWrite(INIT__INFO, 0, "Init", "Login server listening on %s port %i", eqsf.listen_ip_address, net.GetPort());
else
LogWrite(INIT__INFO, 0, "Init", "Login server listening on port %i", net.GetPort());
if (!eqsf.Open(net.GetPort())) {
LogWrite(INIT__ERROR, 0, "Init", "Failed to open port %i.", net.GetPort());
return 1;
}
net.login_running = true;
net.login_uptime = getCurrentTimestamp();
net.UpdateWindowTitle();
EQStream* eqs;
Timer* TimeoutTimer = new Timer(5000);
TimeoutTimer->Start();
while (RunLoops) {
Timer::SetCurrentTime();
while ((eqs = eqsf.Pop())) {
struct in_addr in;
in.s_addr = eqs->GetRemoteIP();
LogWrite(LOGIN__INFO, 0, "Login", "New client from IP: %s on port %i", inet_ntoa(in), ntohs(eqs->GetRemotePort()));
Client* client = new Client(eqs);
eqs->SetClientVersion(0);
client_list.Add(client);
net.numclients++;
net.UpdateWindowTitle();
}
if (TimeoutTimer->Check()) {
eqsf.CheckTimeout();
}
if (statTimer.Check()) {
world_list.UpdateWorldStats();
database.RemoveOldWorldServerStats();
database.FixBugReport();
}
client_list.Process();
world_list.Process();
Sleep(1);
}
eqsf.Close();
world_list.Shutdown();
delete TimeoutTimer;
return 0;
}

307
old/LoginServer/net.hpp Normal file
View File

@ -0,0 +1,307 @@
#pragma once
// Copyright (C) 2007-2021 EQ2Emulator Development Team, GPL v3 License
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <atomic>
#include <iostream>
#include <string>
#include <cstring>
#include <ctime>
#include <csignal>
#include <sstream>
#include <fstream>
#include <map>
#include "lworld.hpp"
#include "client.hpp"
#include "login_database.hpp"
#include "../common/log.hpp"
#include "../common/unix.hpp"
#include "../common/crc16.hpp"
#include "../common/types.hpp"
#include "../common/debug.hpp"
#include "../common/queue.hpp"
#include "../common/timer.hpp"
#include "../common/version.hpp"
#include "../common/separator.hpp"
#include "../common/web_server.hpp"
#include "../common/json_parser.hpp"
#include "../common/data_buffer.hpp"
#include "../common/config_reader.hpp"
#include "../common/misc_functions.hpp"
#include "../common/common_defines.hpp"
#include "../common/packet/packet_struct.hpp"
#include "../common/packet/packet_functions.hpp"
#include "../common/stream/eq_stream_factory.hpp"
void CatchSignal(int sig_num);
enum eServerMode
{
Standalone,
Master,
Slave,
Mesh
};
class NetConnection
{
public:
// Constructor - initializes all network connection parameters and default values
NetConnection()
{
port = 5999;
listening_socket = 0;
memset(masteraddress, 0, sizeof(masteraddress));
uplinkport = 0;
memset(uplinkaccount, 0, sizeof(uplinkaccount));
memset(uplinkpassword, 0, sizeof(uplinkpassword));
LoginMode = Standalone;
Uplink_WrongVersion = false;
numclients = 0;
numservers = 0;
allowAccountCreation = true;
// Full expansion support flag - controls available expansions
expansionFlag = 0x7CFF;
// Cities availability flag - controls which starting cities are available
citiesFlag = 0xFF;
// Default subscription level - controls character creation restrictions
defaultSubscriptionLevel = 0xFFFFFFFF;
// Enabled races flag - controls which races are selectable
enabledRaces = 0xFFFF;
web_loginport = 0;
login_webserver = nullptr;
login_running = false;
login_uptime = getCurrentTimestamp();
}
// Destructor - cleans up web server resources
~NetConnection()
{
safe_delete(login_webserver);
}
// Updates console window title with server status information
void UpdateWindowTitle(char* iNewTitle = 0)
{
// Linux systems don't support console title updates like Windows
// This is a no-op on Linux systems
}
// Reads and parses the login server configuration from JSON file
bool ReadLoginConfig()
{
JsonParser parser(MAIN_CONFIG_FILE);
if (!parser.IsLoaded()) {
LogWrite(INIT__ERROR, 0, "Init", "Failed to find %s in server directory..", MAIN_CONFIG_FILE);
return false;
}
std::string serverport = parser.getValue("loginconfig.serverport");
std::string serverip = parser.getValue("loginconfig.serverip");
if (!parser.convertStringToUnsignedShort(serverport, port)) {
LogWrite(INIT__ERROR, 0, "Init", "Failed to translate loginconfig.serverport..");
return false;
}
if (serverip.size() > 0) {
eqsf.listen_ip_address = new char[serverip.size() + 1];
strcpy(eqsf.listen_ip_address, serverip.c_str());
}
else {
safe_delete(eqsf.listen_ip_address);
eqsf.listen_ip_address = nullptr;
}
std::string acctcreate_str = parser.getValue("loginconfig.accountcreation");
int16 allow_acct = 0;
parser.convertStringToUnsignedShort(acctcreate_str, allow_acct);
allowAccountCreation = allow_acct > 0 ? true : false;
std::string expflag_str = parser.getValue("loginconfig.expansionflag");
parser.convertStringToUnsignedInt(expflag_str, expansionFlag);
std::string citiesflag_str = parser.getValue("loginconfig.citiesflag");
parser.convertStringToUnsignedChar(citiesflag_str, citiesFlag);
std::string defaultsublevel_str = parser.getValue("loginconfig.defaultsubscriptionlevel");
parser.convertStringToUnsignedInt(defaultsublevel_str, defaultSubscriptionLevel);
std::string enableraces_str = parser.getValue("loginconfig.enabledraces");
parser.convertStringToUnsignedInt(enableraces_str, enabledRaces);
web_loginaddress = parser.getValue("loginconfig.webloginaddress");
web_certfile = parser.getValue("loginconfig.webcertfile");
web_keyfile = parser.getValue("loginconfig.webkeyfile");
web_keypassword = parser.getValue("loginconfig.webkeypassword");
web_hardcodeuser = parser.getValue("loginconfig.webhardcodeuser");
web_hardcodepassword = parser.getValue("loginconfig.webhardcodepassword");
std::string webloginport_str = parser.getValue("loginconfig.webloginport");
parser.convertStringToUnsignedShort(webloginport_str, web_loginport);
LogWrite(INIT__INFO, 0, "Init", "%s loaded..", MAIN_CONFIG_FILE);
LogWrite(INIT__INFO, 0, "Init", "Database init begin..");
if (!database.Init()) {
LogWrite(INIT__ERROR, 0, "Init", "Database init FAILED!");
LogStop();
return false;
}
LogWrite(INIT__INFO, 0, "Init", "Loading opcodes 2.0..");
EQOpcodeVersions = database.GetVersions();
std::map<int16,int16>::iterator version_itr2;
int16 version1 = 0;
for (version_itr2 = EQOpcodeVersions.begin(); version_itr2 != EQOpcodeVersions.end(); version_itr2++) {
version1 = version_itr2->first;
EQOpcodeManager[version1] = new RegularOpcodeManager();
std::map<std::string, uint16> eq = database.GetOpcodes(version1);
if (!EQOpcodeManager[version1]->LoadOpcodes(&eq)) {
LogWrite(INIT__ERROR, 0, "Init", "Loading opcodes failed. Make sure you have sourced the opcodes.sql file!");
return false;
}
}
return true;
}
// Displays the EQ2Emulator welcome header with ASCII art and information
void WelcomeHeader()
{
printf("Module: %s, Version: %s", EQ2EMU_MODULE, CURRENT_VERSION);
printf("\n\nCopyright (C) 2007-2021 EQ2Emulator. https://www.eq2emu.com \n\n");
printf("EQ2Emulator is free software: you can redistribute it and/or modify\n");
printf("it under the terms of the GNU General Public License as published by\n");
printf("the Free Software Foundation, either version 3 of the License, or\n");
printf("(at your option) any later version.\n\n");
printf("EQ2Emulator is distributed in the hope that it will be useful,\n");
printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n");
printf("GNU General Public License for more details.\n\n");
printf(" /$$$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$$$ \n");
printf("| $$_____/ /$$__ $$ /$$__ $$| $$_____/ \n");
printf("| $$ | $$ \\ $$|__/ \\ $$| $$ /$$$$$$/$$$$ /$$ /$$\n");
printf("| $$$$$ | $$ | $$ /$$$$$$/| $$$$$ | $$_ $$_ $$| $$ | $$\n");
printf("| $$__/ | $$ | $$ /$$____/ | $$__/ | $$ \\ $$ \\ $$| $$ | $$\n");
printf("| $$ | $$/$$ $$| $$ | $$ | $$ | $$ | $$| $$ | $$\n");
printf("| $$$$$$$$| $$$$$$/| $$$$$$$$| $$$$$$$$| $$ | $$ | $$| $$$$$$/\n");
printf("|________/ \\____ $$$|________/|________/|__/ |__/ |__/ \\______/ \n");
printf(" \\__/ \n\n");
printf(" Website : https://eq2emu.com \n");
printf(" Wiki : https://wiki.eq2emu.com \n");
printf(" Git : https://git.eq2emu.com \n");
printf(" Discord : https://discord.gg/5Cavm9NYQf \n\n");
printf("For more detailed logging, modify 'Level' param the log_config.xml file.\n\n");
fflush(stdout);
}
// Initializes and starts the web server for login status and world information
void InitWebServer(std::string web_ipaddr, int16 web_port, std::string cert_file, std::string key_file, std::string key_password, std::string hardcode_user, std::string hardcode_password)
{
if (web_ipaddr.size() > 0 && web_port > 0) {
try {
login_webserver = new WebServer(web_ipaddr, web_port, cert_file, key_file, key_password, hardcode_user, hardcode_password);
login_webserver->register_route("/status", NetConnection::Web_loginhandle_status);
login_webserver->register_route("/worlds", NetConnection::Web_loginhandle_worlds);
login_webserver->run();
LogWrite(INIT__INFO, 0, "Init", "Login Web Server is listening on %s:%u..", web_ipaddr.c_str(), web_port);
}
catch (const std::exception& e) {
LogWrite(INIT__ERROR, 0, "Init", "Login Web Server failed to listen on %s:%u due to reason %s", web_ipaddr.c_str(), web_port, e.what());
}
}
}
// Accessor methods for configuration values
int16 GetPort() { return port; }
void SetPort(int16 in_port) { port = in_port; }
eServerMode GetLoginMode() { return LoginMode; }
char* GetMasterAddress() { return masteraddress; }
int16 GetUplinkPort() { if (uplinkport != 0) return uplinkport; else return port; }
char* GetUplinkAccount() { return uplinkaccount; }
char* GetUplinkPassword() { return uplinkpassword; }
bool IsAllowingAccountCreation() { return allowAccountCreation; }
int32 GetExpansionFlag() { return expansionFlag; }
int8 GetCitiesFlag() { return citiesFlag; }
int32 GetDefaultSubscriptionLevel() { return defaultSubscriptionLevel; }
int32 GetEnabledRaces() { return enabledRaces; }
std::string GetWebLoginAddress() { return web_loginaddress; }
int16 GetWebLoginPort() { return web_loginport; }
std::string GetWebCertFile() { return web_certfile; }
std::string GetWebKeyFile() { return web_keyfile; }
std::string GetWebKeyPassword() { return web_keypassword; }
std::string GetWebHardcodeUser() { return web_hardcodeuser; }
std::string GetWebHardcodePassword() { return web_hardcodepassword; }
// Web server route handlers for status and world information
static void Web_loginhandle_status(const http::request<http::string_body>& req, http::response<http::string_body>& res);
static void Web_loginhandle_worlds(const http::request<http::string_body>& req, http::response<http::string_body>& res);
// Public server state variables
char address[1024];
int32 numclients;
int32 numservers;
bool login_running;
std::atomic<int64> login_uptime;
protected:
friend class LWorld;
bool Uplink_WrongVersion;
private:
// Network connection parameters
int16 port;
int listening_socket;
char masteraddress[300];
int16 uplinkport;
char uplinkaccount[300];
char uplinkpassword[300];
eServerMode LoginMode;
// Login server configuration flags
bool allowAccountCreation;
int32 expansionFlag;
int8 citiesFlag;
int32 defaultSubscriptionLevel;
int32 enabledRaces;
// Web server configuration
std::string web_loginaddress;
std::string web_certfile;
std::string web_keyfile;
std::string web_keypassword;
std::string web_hardcodeuser;
std::string web_hardcodepassword;
int16 web_loginport;
WebServer* login_webserver;
};
// Global variables - external declarations
extern EQStreamFactory eqsf;
extern std::map<int16,OpcodeManager*> EQOpcodeManager;
extern NetConnection net;
extern ClientList client_list;
extern LWorldList world_list;
extern LoginDatabase database;
extern ConfigReader configReader;
extern std::map<int16, int16> EQOpcodeVersions;
extern Timer statTimer;
extern volatile bool RunLoops;

View File

@ -0,0 +1,160 @@
// Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) - GPLv3
#pragma once
#include <vector>
#include <string>
#include "lworld.hpp"
#include "login_structs.hpp"
#include "login_database.hpp"
#include "../common/types.hpp"
#include "../common/data_buffer.hpp"
#include "../common/config_reader.hpp"
#include "../common/misc_functions.hpp"
#include "../common/global_headers.hpp"
#include "../common/eq_common_structs.hpp"
#include "../common/packet/eq_packet.hpp"
extern ConfigReader configReader;
extern LWorldList world_list;
extern LoginDatabase database;
using std::vector;
using std::string;
// Character profile data for character selection screen
class CharSelectProfile : public DataBuffer
{
public:
// Constructor - initializes character profile with version-specific packet structure
CharSelectProfile(int16 version)
{
deleted = false;
packet = configReader.getStruct("CharSelectProfile", version);
// Initialize all 24 equipment slots to default values
for (int8 i = 0; i < 24; i++) {
packet->setEquipmentByName("equip", 0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, i);
}
}
// Destructor - safely cleans up packet structure
~CharSelectProfile()
{
safe_delete(packet);
}
// Serializes character profile data for transmission
void SaveData(int16 in_version)
{
Clear();
AddData(*packet->serializeString());
}
PacketStruct* packet; // Packet structure containing character data
int16 size; // Size of serialized data
bool deleted; // Flag indicating if character is marked for deletion
};
// Character selection list containing multiple character profiles
class LS_CharSelectList : public DataBuffer
{
public:
// Serializes character list data with account information for specified client version
EQ2Packet* serialize(int16 version)
{
Clear();
AddData(num_characters);
AddData(char_data);
if (version <= 561) {
// Early client version uses simplified account info structure
LS_CharListAccountInfoEarlyClient account_info;
account_info.account_id = account_id;
account_info.unknown1 = 0xFFFFFFFF;
account_info.unknown2 = 0;
account_info.maxchars = 7; // Live has a max of 7 on gold accounts base
account_info.unknown4 = 0;
AddData(account_info);
} else {
// Later client versions use extended account info structure
LS_CharListAccountInfo account_info;
account_info.account_id = account_id;
account_info.unknown1 = 0xFFFFFFFF;
account_info.unknown2 = 0;
account_info.maxchars = database.GetMaxCharsSetting();
account_info.vet_adv_bonus = database.GetAccountBonus(account_id);
account_info.vet_trade_bonus = 0;
account_info.unknown4 = 0;
for (int i = 0; i < 3; i++)
account_info.unknown5[i] = 0xFFFFFFFF;
account_info.unknown5[3] = 0;
AddData(account_info);
}
return new EQ2Packet(OP_AllCharactersDescReplyMsg, getData(), getDataSize());
}
// Appends character data to the character list
void addChar(uchar* data, int16 size)
{
char_data.append(reinterpret_cast<char*>(data), size);
}
// Loads character data from database and populates character list for specified account
void loadData(int32 account, vector<CharSelectProfile*> charlist, int16 version)
{
account_id = account;
num_characters = 0;
char_data = "";
CharSelectProfile* character = nullptr;
for (auto itr = charlist.begin(); itr != charlist.end(); itr++) {
character = *itr;
int32 serverID = character->packet->getType_int32_ByName("server_id");
if (character->deleted) {
// Workaround for old clients <= 561 that crash if you delete a char
// Doesn't refresh the char panel correctly
character->packet->setDataByName("name", "(deleted)");
character->packet->setDataByName("charid", 0xFFFFFFFF);
character->packet->setDataByName("name", 0xFFFFFFFF);
character->packet->setDataByName("server_id", 0xFFFFFFFF);
character->packet->setDataByName("created_date", 0xFFFFFFFF);
character->packet->setDataByName("unknown1", 0xFFFFFFFF);
character->packet->setDataByName("unknown2", 0xFFFFFFFF);
character->packet->setDataByName("flags", 0xFF);
} else if (serverID == 0 || !world_list.FindByID(serverID)) {
continue;
}
num_characters++;
character->SaveData(version);
addChar(character->getData(), character->getDataSize());
}
}
int8 num_characters; // Number of characters in the list
int32 account_id; // Account ID for this character list
string char_data; // Serialized character data buffer
};
// Request packet for character deletion operations
class LS_DeleteCharacterRequest : public DataBuffer
{
public:
// Loads character deletion request data from incoming packet
void loadData(EQApplicationPacket* packet)
{
InitializeLoadData(packet->pBuffer, packet->size);
LoadData(character_number);
LoadData(server_id);
LoadData(spacer);
LoadDataString(name);
}
int32 character_number; // Character slot number to delete
int32 server_id; // Server ID where character exists
int32 spacer; // Padding/alignment bytes
EQ2_16BitString name; // Character name to delete
};

101
old/LoginServer/web.hpp Normal file
View File

@ -0,0 +1,101 @@
// Copyright (C) 2007-2021 EQ2Emulator Development Team, GPL v3
#pragma once
#include <sstream>
#include <string>
#include <map>
#include <arpa/inet.h>
#include <boost/algorithm/string.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include "net.hpp"
#include "lworld.hpp"
extern ClientList client_list;
extern LWorldList world_list;
extern NetConnection net;
/**
* Handles web requests for login server status information.
* Generates JSON response containing login status, uptime, client count, and world count.
* @param req HTTP request object containing the incoming request
* @param res HTTP response object to populate with JSON status data
*/
void NetConnection::Web_loginhandle_status(const http::request<http::string_body>& req, http::response<http::string_body>& res)
{
// Set response content type to JSON
res.set(http::field::content_type, "application/json");
boost::property_tree::ptree pt;
// Build status information tree
pt.put("web_status", "online");
pt.put("login_status", net.login_running ? "online" : "offline");
pt.put("login_uptime", (getCurrentTimestamp() - net.login_uptime));
// Convert uptime to human-readable format
auto [days, hours, minutes, seconds] = convertTimestampDuration((getCurrentTimestamp() - net.login_uptime));
std::string uptime_str("Days: " + std::to_string(days) + ", " + "Hours: " + std::to_string(hours) + ", " + "Minutes: " + std::to_string(minutes) + ", " + "Seconds: " + std::to_string(seconds));
pt.put("login_uptime_string", uptime_str);
pt.put("world_count", world_list.GetCount(ConType::World));
pt.put("client_count", net.numclients);
// Serialize to JSON and set response body
std::ostringstream oss;
boost::property_tree::write_json(oss, pt);
std::string json = oss.str();
res.body() = json;
res.prepare_payload();
}
/**
* Handles web requests for world server list information.
* Delegates to LWorldList::PopulateWorldList to generate the response.
* @param req HTTP request object containing the incoming request
* @param res HTTP response object to be populated with world server data
*/
void NetConnection::Web_loginhandle_worlds(const http::request<http::string_body>& req, http::response<http::string_body>& res)
{
world_list.PopulateWorldList(res);
}
/**
* Populates HTTP response with JSON data containing all active world servers.
* Iterates through world map and builds JSON array of world server information
* including ID, name, status, player count, and IP address.
* @param res HTTP response object to populate with world server JSON data
*/
void LWorldList::PopulateWorldList(http::response<http::string_body>& res)
{
struct in_addr in;
res.set(http::field::content_type, "application/json");
boost::property_tree::ptree maintree;
std::ostringstream oss;
// Iterate through all worlds in the world map
std::map<int32,LWorld*>::iterator map_list;
for (map_list = worldmap.begin(); map_list != worldmap.end(); map_list++) {
LWorld* world = map_list->second;
in.s_addr = world->GetIP();
// Only include World type servers in the response
if (world->GetType() == World) {
boost::property_tree::ptree pt;
pt.put("id", world->GetID());
pt.put("world_name", world->GetName());
pt.put("status", (world->GetStatus() == 1) ? "online" : "offline");
pt.put("num_players", world->GetPlayerNum());
pt.put("ip_addr", inet_ntoa(in)); // Convert IP to string format
maintree.push_back(std::make_pair("", pt));
}
}
// Build final JSON structure and serialize
boost::property_tree::ptree result;
result.add_child("WorldServers", maintree);
boost::property_tree::write_json(oss, result);
std::string json = oss.str();
res.body() = json;
res.prepare_payload();
}

View File

@ -0,0 +1,87 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include <map>
using namespace std;
// Appearances must use a hash table because of the large amount that exists and the large spacing
// between their ID's. String and character arrays could not be used for the first iterator because
// it would require the same pointer to access it from the hash table, which is obviously not possible
// since the text is from the client.
// maximum amount of iterations it will attempt to find a entree
#define HASH_SEARCH_MAX 20
class Appearance
{
public:
// JA: someday add the min_client_version to the map to determine which appearance_id to set per client version
Appearance(int32 inID, const char *inName, int16 inVer)
{
if( !inName )
return;
name = string(inName);
id = inID;
min_client = inVer;
}
int32 GetID() { return id; }
const char* GetName() { return name.c_str(); }
int16 GetMinClientVersion() { return min_client; }
string GetNameString() { return name; }
private:
int32 id;
string name;
int16 min_client;
};
class Appearances
{
public:
~Appearances(){
Reset();
}
void Reset(){
ClearAppearances();
}
void ClearAppearances(){
map<int32, Appearance*>::iterator map_list;
for(map_list = appearanceMap.begin(); map_list != appearanceMap.end(); map_list++ )
safe_delete(map_list->second);
appearanceMap.clear();
}
void InsertAppearance(Appearance* a){
appearanceMap[a->GetID()] = a;
}
Appearance* FindAppearanceByID(int32 id){
if(appearanceMap.count(id) > 0)
return appearanceMap[id];
return 0;
}
private:
map<int32, Appearance*> appearanceMap;
};

View File

@ -0,0 +1,765 @@
#include "Bot.h"
#include "BotBrain.h"
#include "../Trade.h"
#include "../../common/Log.h"
#include "../Worlddatabase.hpp"
#include "../classes.h"
#include "../SpellProcess.h"
extern WorldDatabase database;
extern MasterSpellList master_spell_list;
extern World world;
extern Classes classes;
Bot::Bot() : NPC() {
SetBrain(new BotBrain(this));
BotID = 0;
ShowHelm = true;
ShowCloak = true;
CanTaunt = false;
combat_target = 0;
main_tank = 0;
//AddPrimaryEntityCommand("hail", 10000, "hail", "", 0, 0);
AddSecondaryEntityCommand("invite bot", 10000, "invite", "", 0, 0);
AddSecondaryEntityCommand("bot inventory", 10000, "bot inv list", "", 0, 0);
InfoStruct* info = GetInfoStruct();
info->set_str_base(50);
info->set_sta_base(20);
info->set_wis_base(20);
info->set_intel_base(20);
info->set_agi_base(20);
camping = false;
immediate_camp = false;
}
Bot::~Bot() {
}
void Bot::GiveItem(int32 item_id) {
Item* master_item = master_item_list.GetItem(item_id);
Item* item = 0;
if (master_item)
item = new Item(master_item);
if (item) {
int8 slot = GetEquipmentList()->GetFreeSlot(item);
if (slot != 255) {
GetEquipmentList()->AddItem(slot, item);
SetEquipment(item, slot);
database.SaveBotItem(BotID, item_id, slot);
if (slot == 0) {
ChangePrimaryWeapon();
if (IsBot())
LogWrite(PLAYER__ERROR, 0, "Bot", "Changing bot primary weapon.");
}
CalculateBonuses();
}
}
}
void Bot::GiveItem(Item* item) {
if (item) {
int8 slot = GetEquipmentList()->GetFreeSlot(item);
if (slot != 255) {
GetEquipmentList()->AddItem(slot, item);
SetEquipment(item, slot);
database.SaveBotItem(BotID, item->details.item_id, slot);
if (slot == 0) {
ChangePrimaryWeapon();
if (IsBot())
LogWrite(PLAYER__ERROR, 0, "Bot", "Changing bot primary weapon.");
}
CalculateBonuses();
}
}
}
void Bot::RemoveItem(Item* item) {
int8 slot = GetEquipmentList()->GetSlotByItem(item);
if (slot != 255) {
GetEquipmentList()->RemoveItem(slot, true);
SetEquipment(0, slot);
}
}
void Bot::TradeItemAdded(Item* item) {
int8 slot = GetEquipmentList()->GetFreeSlot(item);
if (slot == 255 && item->slot_data.size() > 0) {
slot = item->slot_data[0];
AddItemToTrade(slot);
}
}
void Bot::AddItemToTrade(int8 slot) {
Item* item = GetEquipmentList()->GetItem(slot);
if (trading_slots.count(slot) == 0 && item && trade) {
trade->AddItemToTrade(this, item, 1, 255);
trading_slots.insert(slot);
}
}
bool Bot::CheckTradeItems(map<int8, TradeItemInfo>* list) {
if (!list) {
LogWrite(PLAYER__ERROR, 0, "Bot", "CheckTradeItems did not recieve a valid list of items");
return false;
}
bool ret = true;
map<int8, TradeItemInfo>::iterator itr;
for (itr = list->begin(); itr != list->end(); itr++) {
Item* item = itr->second.item;
if (item) {
if (!CanEquipItem(item)) {
// No slots means not equipable so reject the trade
ret = false;
break;
}
}
}
return ret;
}
void Bot::FinishTrade() {
trading_slots.clear();
}
bool Bot::CanEquipItem(Item* item) {
if (item) {
if (item->IsArmor() || item->IsWeapon() || item->IsFood() || item->IsRanged() || item->IsShield() || item->IsBauble() || item->IsAmmo() || item->IsThrown()) {
int16 override_level = item->GetOverrideLevel(GetAdventureClass(), GetTradeskillClass());
if (override_level > 0 && override_level <= GetLevel()) {
LogWrite(PLAYER__ERROR, 0, "Bot", "Passed in override_level check");
return true;
}
if (item->CheckClass(GetAdventureClass(), GetTradeskillClass()))
if (item->CheckLevel(GetAdventureClass(), GetTradeskillClass(), GetLevel())) {
LogWrite(PLAYER__ERROR, 0, "Bot", "Passed in normal check");
return true;
}
}
}
return false;
}
void Bot::MessageGroup(string msg) {
GroupMemberInfo* gmi = GetGroupMemberInfo();
if (gmi)
world.GetGroupManager()->GroupChatMessage(gmi->group_id, this, 0, msg.c_str());
}
void Bot::GetNewSpells() {
vector<Spell*> spells;
vector<Spell*>* spells1 = master_spell_list.GetSpellListByAdventureClass(GetAdventureClass(), (double)GetLevel(), 1);
vector<Spell*>* spells2 = master_spell_list.GetSpellListByAdventureClass(classes.GetBaseClass(GetAdventureClass()), (double)GetLevel(), 1);
vector<Spell*>* spells3 = master_spell_list.GetSpellListByAdventureClass(classes.GetSecondaryBaseClass(GetAdventureClass()), (double)GetLevel(), 1);
spells.insert(spells.end(), spells1->begin(), spells1->end());
spells.insert(spells.end(), spells2->begin(), spells2->end());
spells.insert(spells.end(), spells3->begin(), spells3->end());
vector<Spell*>::iterator itr;
map<int32, int8>* spell_list = 0;
for (itr = spells.begin(); itr != spells.end(); itr++) {
switch ((*itr)->GetSpellData()->spell_type) {
case SPELL_TYPE_DD:
spell_list = &dd_spells;
break;
case SPELL_TYPE_DOT:
spell_list = &dot_spells;
break;
case SPELL_TYPE_HEAL:
spell_list = &heal_spells;
break;
case SPELL_TYPE_HOT_WARD:
spell_list = &hot_ward_spells;
break;
case SPELL_TYPE_DEBUFF:
spell_list = &debuff_spells;
break;
case SPELL_TYPE_BUFF:
spell_list = &buff_spells;
break;
case SPELL_TYPE_COMBATBUFF:
spell_list = &combat_buff_spells;
break;
case SPELL_TYPE_TAUNT:
spell_list = &taunt_spells;
break;
case SPELL_TYPE_DETAUNT:
spell_list = &detaunt_spells;
break;
case SPELL_TYPE_REZ:
LogWrite(PLAYER__ERROR, 0, "Bot", "Adding rez spell.");
spell_list = &rez_spells;
break;
case SPELL_TYPE_CURE:
spell_list = &cure_spells;
break;
default:
spell_list = 0;
break;
}
if (spell_list && spell_list->count((*itr)->GetSpellID()) == 0)
(*spell_list)[(*itr)->GetSpellID()] = 1;
}
safe_delete(spells1);
safe_delete(spells2);
safe_delete(spells3);
}
Entity* Bot::GetCombatTarget() {
Spawn* target = GetZone()->GetSpawnByID(combat_target);
if (target && target->IsEntity())
return (Entity*)target;
combat_target = 0;
return 0;
}
Spell* Bot::SelectSpellToCast(float distance) {
Spell* spell = 0;
map<int32, int8>::iterator itr;
// Heal
spell = GetHealSpell();
if (spell)
return spell;
// Taunt
spell = GetTauntSpell();
if (spell)
return spell;
// Detaunt
spell = GetDetauntSpell();
if (spell)
return spell;
// Hot/Ward
spell = GetHoTWardSpell();
if (spell)
return spell;
// Debuff
spell = GetDebuffSpell();
if (spell)
return spell;
// Combat Buff
spell = GetCombatBuffSpell();
if (spell)
return spell;
// DoT
spell = GetDoTSpell();
if (spell)
return spell;
// DD
spell = GetDDSpell();
if (spell)
return spell;
return 0;
}
void Bot::SetRecast(Spell* spell, int32 time) {
recast_times[spell->GetSpellID()] = time;
}
bool Bot::IsSpellReady(Spell* spell) {
if (recast_times.count(spell->GetSpellID()) > 0) {
if (recast_times[spell->GetSpellID()] > Timer::GetCurrentTime2())
return false;
}
return true;
}
Spell* Bot::GetDDSpell() {
if(world.IsReloadingSubsystems())
return nullptr;
if (dd_spells.size() == 0)
return 0;
Spell* spell = 0;
map<int32, int8>::iterator itr;
for (itr = dd_spells.begin(); itr != dd_spells.end(); itr++) {
spell = master_spell_list.GetSpell(itr->first, itr->second);
if (spell && IsSpellReady(spell)) {
return spell;
}
}
return 0;
}
Spell* Bot::GetHealSpell() {
if(world.IsReloadingSubsystems())
return nullptr;
if (heal_spells.size() == 0)
return 0;
// Get an available heal spell
Spell* spell = 0;
map<int32, int8>::iterator itr;
for (itr = heal_spells.begin(); itr != heal_spells.end(); itr++) {
spell = master_spell_list.GetSpell(itr->first, itr->second);
if (spell && IsSpellReady(spell)) {
break;
}
}
// No heal available, return out
if (!spell)
return 0;
// There was a heal spell so find a group member that needs healing
int8 threshold = GetHealThreshold();
GroupMemberInfo* gmi = GetGroupMemberInfo();
PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id);
if (group)
{
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* members = group->GetMembers();
for (int8 i = 0; i < members->size(); i++) {
Entity* member = members->at(i)->member;
if(!member)
continue;
if (!member->Alive())
continue;
int8 percent = (int8)(((float)member->GetHP() / member->GetTotalHP()) * 100);
if (percent <= threshold) {
if (spell) {
SetTarget(member);
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
return spell;
}
}
}
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
}
return 0;
}
Spell* Bot::GetTauntSpell() {
if(world.IsReloadingSubsystems())
return nullptr;
if (taunt_spells.size() == 0)
return 0;
// If not the main tank and taunts are turned off return out
if (main_tank != this && !CanTaunt)
return 0;
Spell* spell = 0;
map<int32, int8>::iterator itr;
for (itr = taunt_spells.begin(); itr != taunt_spells.end(); itr++) {
spell = master_spell_list.GetSpell(itr->first, itr->second);
if (spell && IsSpellReady(spell)) {
return spell;
}
}
return 0;
}
Spell* Bot::GetDetauntSpell() {
if(world.IsReloadingSubsystems())
return nullptr;
if (detaunt_spells.size() == 0)
return 0;
if (!GetTarget() || !GetTarget()->IsNPC())
return 0;
NPC* target = (NPC*)GetTarget();
Entity* hated = target->Brain()->GetMostHated();
if (hated == this) {
Spell* spell = 0;
map<int32, int8>::iterator itr;
for (itr = detaunt_spells.begin(); itr != detaunt_spells.end(); itr++) {
spell = master_spell_list.GetSpell(itr->first, itr->second);
if (spell && IsSpellReady(spell)) {
return spell;
}
}
}
return 0;
}
Spell* Bot::GetHoTWardSpell() {
if(world.IsReloadingSubsystems())
return nullptr;
if (hot_ward_spells.size() == 0)
return 0;
// Get an available spell
Spell* spell = 0;
map<int32, int8>::iterator itr;
for (itr = hot_ward_spells.begin(); itr != hot_ward_spells.end(); itr++) {
spell = master_spell_list.GetSpell(itr->first, itr->second);
if (spell && IsSpellReady(spell)) {
break;
}
}
// No spell available, return out
if (!spell)
return 0;
// There was a spell so find a group member that needs healing
int8 threshold = GetHealThreshold();
GroupMemberInfo* gmi = GetGroupMemberInfo();
PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id);
if (group)
{
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* members = group->GetMembers();
for (int8 i = 0; i < members->size(); i++) {
Entity* member = members->at(i)->member;
if(!member)
continue;
int8 percent = 0;
if (member->GetHP() > 0)
percent = (int8)(((float)member->GetHP() / member->GetTotalHP()) * 100);
if (percent <= 99 && percent > threshold) {
if (spell) {
SetTarget(member);
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
return spell;
}
}
}
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
}
return 0;
}
Spell* Bot::GetDebuffSpell() {
if(world.IsReloadingSubsystems())
return nullptr;
if (debuff_spells.size() == 0)
return 0;
Spell* spell = 0;
map<int32, int8>::iterator itr;
for (itr = debuff_spells.begin(); itr != debuff_spells.end(); itr++) {
spell = master_spell_list.GetSpell(itr->first, itr->second);
if (spell && IsSpellReady(spell)) {
// If target already has this effect on them then continue to the next spell
if (((Entity*)GetTarget())->GetSpellEffect(itr->first))
continue;
return spell;
}
}
return 0;
}
Spell* Bot::GetCombatBuffSpell() {
if(world.IsReloadingSubsystems())
return nullptr;
return 0;
}
Spell* Bot::GetDoTSpell() {
if(world.IsReloadingSubsystems())
return nullptr;
if (dot_spells.size() == 0)
return 0;
Spell* spell = 0;
map<int32, int8>::iterator itr;
for (itr = dot_spells.begin(); itr != dot_spells.end(); itr++) {
spell = master_spell_list.GetSpell(itr->first, itr->second);
if (spell && IsSpellReady(spell)) {
// If target already has this effect on them then continue to the next spell
if (((Entity*)GetTarget())->GetSpellEffect(itr->first))
continue;
return spell;
}
}
return 0;
}
Spell* Bot::GetBuffSpell() {
if(world.IsReloadingSubsystems())
return nullptr;
if (buff_spells.size() == 0)
return 0;
Spell* spell = 0;
Entity* target = 0;
map<int32, int8>::iterator itr;
for (itr = buff_spells.begin(); itr != buff_spells.end(); itr++) {
spell = master_spell_list.GetSpell(itr->first, itr->second);
if (spell && IsSpellReady(spell)) {
target = 0;
if (spell->GetSpellData()->target_type == SPELL_TARGET_SELF)
target = this;
if (spell->GetSpellData()->target_type == SPELL_TARGET_GROUP_AE)
target = this;
if (spell->GetSpellData()->target_type == SPELL_TARGET_ENEMY && spell->GetSpellData()->friendly_spell == 1)
target = (main_tank != NULL) ? main_tank : GetOwner();
if (!target)
continue;
if (!target->Alive())
continue;
// If target already has this effect on them then continue to the next spell
if (target->GetSpellEffect(itr->first))
continue;
SetTarget(target);
return spell;
}
}
return 0;
}
Spell* Bot::GetRezSpell() {
if(world.IsReloadingSubsystems())
return nullptr;
if (rez_spells.size() == 0)
return 0;
GroupMemberInfo* gmi = GetGroupMemberInfo();
if (!gmi)
return 0;
Entity* target = 0;
PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id);
if (group)
{
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* members = group->GetMembers();
for (int8 i = 0; i < members->size(); i++) {
Entity* member = members->at(i)->member;
if (member && !member->Alive() && member->IsPlayer()) {
PendingResurrection* rez = members->at(i)->client->GetCurrentRez();
if (rez->active)
continue;
target = member;
break;
}
}
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
}
if (!target)
return 0;
Spell* spell = 0;
map<int32, int8>::iterator itr;
for (itr = rez_spells.begin(); itr != rez_spells.end(); itr++) {
spell = master_spell_list.GetSpell(itr->first, itr->second);
if (spell && IsSpellReady(spell)) {
SetTarget(target);
return spell;
}
}
return 0;
}
Spell* Bot::GetCureSpell() {
if(world.IsReloadingSubsystems())
return nullptr;
return 0;
}
int8 Bot::GetHealThreshold() {
int8 ret = 0;
switch (GetAdventureClass()) {
case PRIEST:
case CLERIC:
case TEMPLAR:
case INQUISITOR:
case DRUID:
case WARDEN:
case FURY:
case SHAMAN:
case MYSTIC:
case DEFILER:
ret = 70;
break;
default:
ret = 30;
break;
}
return ret;
}
bool Bot::ShouldMelee() {
bool ret = true;
switch (GetAdventureClass()) {
case PRIEST:
case CLERIC:
case TEMPLAR:
case INQUISITOR:
case DRUID:
case WARDEN:
case FURY:
case SHAMAN:
case MYSTIC:
case DEFILER:
case MAGE:
case SORCERER:
case WIZARD:
case WARLOCK:
case ENCHANTER:
case ILLUSIONIST:
case COERCER:
case SUMMONER:
case CONJUROR:
case NECROMANCER:
ret = false;
break;
default:
ret = true;
break;
}
if (GetTarget() == GetOwner())
ret = false;
return ret;
}
void Bot::Camp(bool immediate) {
// Copy from COMMAND_GROUP_LEAVE
camping = true;
immediate_camp = immediate;
}
void Bot::ChangeLevel(int16 old_level, int16 new_level) {
if (new_level < 1)
return;
if (GetLevel() != new_level) {
SetLevel(new_level);
if (GetGroupMemberInfo()) {
UpdateGroupMemberInfo();
world.GetGroupManager()->SendGroupUpdate(GetGroupMemberInfo()->group_id);
}
}
if (GetPet()) {
NPC* pet = (NPC*)GetPet();
if (pet->GetMaxPetLevel() == 0 || new_level <= pet->GetMaxPetLevel()) {
pet->SetLevel(new_level);
GetZone()->PlayAnimation(pet, 1753);
}
}
// level up animation
GetZone()->PlayAnimation(this, 1753);
//player->GetSkills()->IncreaseAllSkillCaps(5 * (new_level - old_level));
GetNewSpells();
//SendNewSpells(player->GetAdventureClass());
//SendNewSpells(classes.GetBaseClass(player->GetAdventureClass()));
//SendNewSpells(classes.GetSecondaryBaseClass(player->GetAdventureClass()));
GetInfoStruct()->set_level(new_level);
UpdateWeapons();
// GetPlayer()->SetLevel(new_level);
LogWrite(MISC__TODO, 1, "TODO", "Get new HP/POWER/stat based on default values from DB\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__);
SetTotalHPBase(new_level*new_level * 2 + 40);
SetTotalPowerBase((sint32)(new_level*new_level*2.1 + 45));
CalculateBonuses();
SetHP(GetTotalHP());
SetPower(GetTotalPower());
GetInfoStruct()->set_agi_base(new_level * 2 + 15);
GetInfoStruct()->set_intel_base(new_level * 2 + 15);
GetInfoStruct()->set_wis_base(new_level * 2 + 15);
GetInfoStruct()->set_str_base(new_level * 2 + 15);
GetInfoStruct()->set_sta_base(new_level * 2 + 15);
GetInfoStruct()->set_cold_base((int16)(new_level*1.5 + 10));
GetInfoStruct()->set_heat_base((int16)(new_level*1.5 + 10));
GetInfoStruct()->set_disease_base((int16)(new_level*1.5 + 10));
GetInfoStruct()->set_mental_base((int16)(new_level*1.5 + 10));
GetInfoStruct()->set_magic_base((int16)(new_level*1.5 + 10));
GetInfoStruct()->set_divine_base((int16)(new_level*1.5 + 10));
GetInfoStruct()->set_poison_base((int16)(new_level*1.5 + 10));
/*UpdateTimeStampFlag(LEVEL_UPDATE_FLAG);
GetPlayer()->SetCharSheetChanged(true);
Message(CHANNEL_COLOR_EXP, "You are now level %i!", new_level);
LogWrite(WORLD__DEBUG, 0, "World", "Player: %s leveled from %u to %u", GetPlayer()->GetName(), old_level, new_level);
GetPlayer()->GetSkills()->SetSkillCapsByType(1, 5 * new_level);
GetPlayer()->GetSkills()->SetSkillCapsByType(3, 5 * new_level);
GetPlayer()->GetSkills()->SetSkillCapsByType(6, 5 * new_level);
GetPlayer()->GetSkills()->SetSkillCapsByType(13, 5 * new_level);
*/
}
void Bot::Begin_Camp() {
GroupMemberInfo* gmi = GetGroupMemberInfo();
if (gmi) {
int32 group_id = gmi->group_id;
world.GetGroupManager()->RemoveGroupMember(group_id, this);
if (!world.GetGroupManager()->IsGroupIDValid(group_id)) {
// leader->Message(CHANNEL_COLOR_GROUP, "%s has left the group.", client->GetPlayer()->GetName());
}
else {
world.GetGroupManager()->GroupMessage(group_id, "%s has left the group.", GetName());
}
}
if(!immediate_camp)
{
GetZone()->PlayAnimation(this, 538);
SetVisualState(540);
GetZone()->Despawn(this, 5000);
}
camping = false;
immediate_camp = true;
if (!GetOwner())
return;
if (GetOwner()->IsPlayer())
((Player*)GetOwner())->SpawnedBots.erase(BotIndex);
}

View File

@ -0,0 +1,95 @@
#pragma once
#include "../NPC.h"
#include <set>
struct TradeItemInfo;
class Bot : public NPC {
public:
Bot();
~Bot();
int32 BotID; // DB id
int32 BotIndex; // Bot id with its owner (player)
bool IsBot() { return true; }
void GiveItem(int32 item_id);
void GiveItem(Item* item);
void RemoveItem(Item* item);
void TradeItemAdded(Item* item);
void AddItemToTrade(int8 slot);
bool CheckTradeItems(map<int8, TradeItemInfo>* list);
void FinishTrade();
void GetNewSpells();
map<int32, int8>* GetBotSpells() { return &dd_spells; }
bool ShowHelm;
bool ShowCloak;
bool CanTaunt;
Entity* GetCombatTarget();
void SetCombatTarget(int32 target) { combat_target = target; }
Spell* SelectSpellToCast(float distance);
void MessageGroup(string msg);
void SetRecast(Spell* spell, int32 time);
bool ShouldMelee();
Spell* GetNextBuffSpell(Spawn* target = 0) { return GetBuffSpell(); }
Spell* GetHealSpell();
Spell* GetRezSpell();
void SetMainTank(Entity* tank) { main_tank = tank; }
void Camp(bool immediate=false);
void ChangeLevel(int16 old_level, int16 new_level);
bool IsCamping() { return camping; }
bool IsImmediateCamp() { return immediate_camp; }
void Begin_Camp();
private:
bool CanEquipItem(Item* item);
bool IsSpellReady(Spell* spell);
Spell* GetTauntSpell();
Spell* GetDetauntSpell();
Spell* GetHoTWardSpell();
Spell* GetDebuffSpell();
Spell* GetCombatBuffSpell();
Spell* GetDoTSpell();
Spell* GetDDSpell();
Spell* GetBuffSpell();
Spell* GetCureSpell();
int8 GetHealThreshold();
set<int8> trading_slots;
int32 combat_target;
Entity* main_tank;
map<int32, int8> bot_spells;
map<int32, int8> dd_spells;
map<int32, int8> dot_spells;
map<int32, int8> heal_spells;
map<int32, int8> hot_ward_spells;
map<int32, int8> debuff_spells;
map<int32, int8> buff_spells;
map<int32, int8> combat_buff_spells;
map<int32, int8> taunt_spells;
map<int32, int8> detaunt_spells;
map<int32, int8> rez_spells;
map<int32, int8> cure_spells;
// First int32 = spell id (change to timer id later), second int32 is time the spell is available to cast again
map<int32, int32> recast_times;
std::atomic<bool> camping;
std::atomic<bool> immediate_camp;
};

View File

@ -0,0 +1,205 @@
#include "BotBrain.h"
#include "../Combat.h"
#include "../Spells.h"
#include "../../common/Log.h"
#include "../Rules/Rules.h"
extern RuleManager rule_manager;
BotBrain::BotBrain(Bot* body) : Brain(body) {
Body = body;
}
BotBrain::~BotBrain() {
}
void BotBrain::Think() {
// No ownder do nothing, probably despawn as owner should never be empty for bots
if (!m_body->GetOwner())
return;
// Not in a group yet then do nothing
if (!m_body->GetGroupMemberInfo())
return;
if (!Body->Alive())
return;
if (Body->IsMezzedOrStunned())
return;
// If combat was processed we can return out
if (ProcessCombat())
return;
// Combat failed to process so do out of combat tasks like follow the player
if (ProcessOutOfCombatSpells())
return;
// put htis here so bots don't try to follow the owner while in combat
if (Body->EngagedInCombat())
return;
// Set target to owner
Spawn* target = GetBody()->GetFollowTarget();
if(target)
{
// Get distance from the owner
float distance = GetBody()->GetDistance(target);
// If out of melee range then move closer
if (distance > rule_manager.GetZoneRule(GetBody()->GetZoneID(), R_Combat, MaxCombatRange)->GetFloat())
MoveCloser(target);
}
}
bool BotBrain::ProcessCombat() {
SetTarget();
if (Body->GetTarget() && Body->EngagedInCombat()) {
if (Body->GetTarget() && Body->GetTarget()->IsEntity() && Body->AttackAllowed((Entity*)Body->GetTarget())) {
Entity* target = (Entity*)Body->GetTarget();
float distance = Body->GetDistance(target);
if (!ProcessSpell(target, distance)) {
if (Body->ShouldMelee())
ProcessMelee(target, distance);
}
NPC* pet = (NPC*)Body->GetPet();
if (pet) {
if (pet->Brain()->GetHate(target) == 0)
pet->AddHate(target, 1);
}
}
return true;
}
return false;
}
void BotBrain::SetTarget() {
// The target issued from /bot attack
if (Body->GetCombatTarget() && Body->GetCombatTarget()->Alive()) {
Body->SetTarget(Body->GetCombatTarget());
Body->InCombat(true);
return;
}
// Assist
Entity* owner = Body->GetOwner();
if (owner && owner->EngagedInCombat()) {
if (owner->GetTarget() && owner->GetTarget()->IsEntity() && owner->GetTarget()->Alive() && owner->AttackAllowed((Entity*)owner->GetTarget())) {
Body->SetTarget(owner->GetTarget());
Body->InCombat(true);
// Add some hate to keep the bot attacking if
// the player toggles combat off
if (GetHate((Entity*)Body->GetTarget()) == 0)
AddHate((Entity*)Body->GetTarget(), 1);
return;
}
}
// Most hated
Entity* hated = GetMostHated();
if (hated && hated->Alive()) {
if (hated == Body->GetOwner()) {
ClearHate(hated);
}
else {
Body->SetTarget(hated);
Body->InCombat(true);
return;
}
}
// None of the above true so clear target and turn combat off
Body->SetTarget(0);
Body->InCombat(false);
}
bool BotBrain::ProcessSpell(Entity* target, float distance) {
if (Body->IsStifled() || Body->IsFeared())
return false;
if (Body->IsCasting())
return false;
if (!HasRecovered())
return false;
Spell* spell = Body->SelectSpellToCast(distance);
if (spell) {
// Target can change (heals for example) so recalculate distance and if out of range move closer
float distance = Body->GetDistance(Body->GetTarget());
if (distance > spell->GetSpellData()->range) {
if (Body->GetTarget()->IsEntity())
MoveCloser((Spawn*)Body->GetTarget());
}
else {
// stop movement if spell can't be cast while moving
if (!spell->GetSpellData()->cast_while_moving)
Body->CalculateRunningLocation(true);
Body->GetZone()->ProcessSpell(spell, Body, Body->GetTarget());
m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000);
// recast time
int32 time = Timer::GetCurrentTime2() + (spell->CalculateRecastTimer(Body));
Body->SetRecast(spell, time);
string str = "I am casting ";
str += spell->GetName();
Body->MessageGroup(str);
}
return true;
}
return false;
}
bool BotBrain::ProcessOutOfCombatSpells() {
if (Body->IsStifled() || Body->IsFeared())
return false;
if (Body->IsCasting())
return false;
if (!HasRecovered())
return false;
Spell* spell = Body->GetHealSpell();
if (!spell)
spell = Body->GetRezSpell();
if (!spell)
spell = Body->GetNextBuffSpell();
if (spell) {
// stop movement if spell can't be cast while moving
if (!spell->GetSpellData()->cast_while_moving)
Body->CalculateRunningLocation(true);
// See if we are in range of target, if not move closer
float distance = Body->GetDistance(Body->GetTarget());
if (distance > spell->GetSpellData()->range) {
if (Body->GetTarget()->IsEntity())
MoveCloser((Spawn*)Body->GetTarget());
}
else {
Body->GetZone()->ProcessSpell(spell, Body, Body->GetTarget());
m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000);
// recast time
int32 time = Timer::GetCurrentTime2() + (spell->CalculateRecastTimer(Body));
Body->SetRecast(spell, time);
string str = "I am casting ";
str += spell->GetName();
Body->MessageGroup(str);
}
return true;
}
return false;
}

View File

@ -0,0 +1,20 @@
#pragma once
#include "../NPC_AI.h"
#include "Bot.h"
class BotBrain : public Brain {
public:
BotBrain(Bot* body);
virtual ~BotBrain();
void Think();
bool ProcessSpell(Entity* target, float distance);
bool ProcessOutOfCombatSpells();
private:
Bot* Body;
bool ProcessCombat();
void SetTarget();
};

View File

@ -0,0 +1,834 @@
#include "../Commands/Commands.h"
#include "../Worlddatabase.hpp"
#include "../classes.h"
#include "../races.h"
#include "../Bots/Bot.h"
#include "../../common/Log.h"
#include "../Trade.h"
#include "../PlayerGroups.h"
#include "../World.h"
#include "../../common/GlobalHeaders.h"
extern WorldDatabase database;
extern ConfigReader configReader;
extern World world;
extern MasterSpellList master_spell_list;
void Commands::Command_Bot(Client* client, Seperator* sep) {
if (sep && sep->IsSet(0)) {
if (strncasecmp("camp", sep->arg[0], 4) == 0) {
if (!client->GetPlayer()->GetTarget() || !client->GetPlayer()->GetTarget()->IsBot()) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot");
return;
}
Bot* bot = (Bot*)client->GetPlayer()->GetTarget();
if (bot->GetOwner() != client->GetPlayer()) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only camp your own bots.");
return;
}
bot->Camp();
return;
}
else if (strncasecmp("attack", sep->arg[0], 6) == 0) {
if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsEntity() && client->GetPlayer()->GetTarget()->Alive()) {
Entity* target = (Entity*)client->GetPlayer()->GetTarget();
if (client->GetPlayer()->GetDistance(target) <= 50) {
if (client->GetPlayer()->AttackAllowed(target)) {
GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo();
if (gmi) {
PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id);
if (group)
{
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* members = group->GetMembers();
deque<GroupMemberInfo*>::iterator itr;
for (itr = members->begin(); itr != members->end(); itr++) {
//devn00b compile says this is no good, commenting out for now.
//if(!member)
// continue;
if ((*itr)->member && (*itr)->member->IsBot() && ((Bot*)(*itr)->member)->GetOwner() == client->GetPlayer()) {
((Bot*)(*itr)->member)->SetCombatTarget(target->GetID());
}
}
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
}
}
}
else
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can not attack that target.");
}
else
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Target is to far away.");
}
else
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Not a valid target.");
return;
}
else if (strncasecmp("spells", sep->arg[0], 6) == 0) {
if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsBot()) {
Bot* bot = (Bot*)client->GetPlayer()->GetTarget();
map<int32, int8>* spells = bot->GetBotSpells();
map<int32, int8>::iterator itr;
string output;
for (itr = spells->begin(); itr != spells->end(); itr++) {
Spell* spell = master_spell_list.GetSpell(itr->first, itr->second);
if (spell) {
output += spell->GetName();
output += "\n";
}
}
client->SimpleMessage(CHANNEL_COLOR_YELLOW, output.c_str());
return;
}
}
else if (strncasecmp("maintank", sep->arg[0], 8) == 0) {
if (!client->GetPlayer()->GetTarget() || !client->GetPlayer()->GetTarget()->IsEntity()) {
client->SimpleMessage(CHANNEL_COMMAND_TEXT, "Not a valid target.");
return;
}
GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo();
if (!gmi) {
client->SimpleMessage(CHANNEL_COMMAND_TEXT, "You are not in a group.");
return;
}
Entity* target = (Entity*)client->GetPlayer()->GetTarget();
if (!world.GetGroupManager()->IsInGroup(gmi->group_id, target)) {
client->SimpleMessage(CHANNEL_COMMAND_TEXT, "Target is not in your group.");
return;
}
PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id);
if (group)
{
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* members = group->GetMembers();
for (int8 i = 0; i < members->size(); i++) {
GroupMemberInfo* gmi2 = members->at(i);
if(!gmi2 || !gmi2->member)
continue;
if (gmi2->member->IsBot() && ((Bot*)gmi2->member)->GetOwner() == client->GetPlayer()) {
((Bot*)gmi2->member)->SetMainTank(target);
client->Message(CHANNEL_COMMAND_TEXT, "Setting main tank for %s to %s", gmi2->member->GetName(), target->GetName());
}
}
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
}
return;
}
else if (strncasecmp("delete", sep->arg[0], 6) == 0) {
if (sep->IsSet(1) && sep->IsNumber(1)) {
int32 index = atoi(sep->arg[1]);
// Check if bot is currently spawned and if so camp it out
if (client->GetPlayer()->SpawnedBots.count(index) > 0) {
Spawn* bot = client->GetCurrentZone()->GetSpawnByID(client->GetPlayer()->SpawnedBots[index]);
if (bot && bot->IsBot())
((Bot*)bot)->Camp();
}
database.DeleteBot(client->GetCharacterID(), index);
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Bot has been deleted.");
return;
}
else {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must give the id (from /bot list) to delete a bot");
return;
}
}
else if (strncasecmp("follow", sep->arg[0], 6) == 0) {
if (sep->IsSet(1) && sep->IsNumber(1)) {
int32 index = atoi(sep->arg[1]);
// Check if bot is currently spawned and if so camp it out
if (client->GetPlayer()->SpawnedBots.count(index) > 0) {
Spawn* bot = client->GetCurrentZone()->GetSpawnByID(client->GetPlayer()->SpawnedBots[index]);
if (bot && bot->IsBot())
((Bot*)bot)->SetFollowTarget(client->GetPlayer(), 5);
}
return;
}
else {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must give the id (from /bot list) to have a bot follow you");
return;
}
}
else if (strncasecmp("stopfollow", sep->arg[0], 10) == 0) {
if (sep->IsSet(1) && sep->IsNumber(1)) {
int32 index = atoi(sep->arg[1]);
// Check if bot is currently spawned and if so camp it out
if (client->GetPlayer()->SpawnedBots.count(index) > 0) {
Spawn* bot = client->GetCurrentZone()->GetSpawnByID(client->GetPlayer()->SpawnedBots[index]);
if (bot && bot->IsBot())
((Bot*)bot)->SetFollowTarget(nullptr);
}
return;
}
else {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must give the id (from /bot list) to stop a following bot");
return;
}
}
else if (strncasecmp("summon", sep->arg[0], 6) == 0) {
if (sep->IsSet(1) && strncasecmp("group", sep->arg[1], 5) == 0) {
GroupMemberInfo* gmi = client->GetPlayer()->GetGroupMemberInfo();
if (gmi) {
Player* player = client->GetPlayer();
PlayerGroup* group = world.GetGroupManager()->GetGroup(gmi->group_id);
if (group)
{
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* members = group->GetMembers();
for (int8 i = 0; i < members->size(); i++) {
Entity* member = members->at(i)->member;
if(!member)
continue;
if (member->IsBot() && ((Bot*)member)->GetOwner() == player) {
if(member->GetZone() && member->GetLocation() != player->GetLocation()) {
member->SetLocation(player->GetLocation());
}
member->SetX(player->GetX());
member->SetY(player->GetY());
member->SetZ(player->GetZ());
client->Message(CHANNEL_COLOR_YELLOW, "Summoning %s.", member->GetName());
}
}
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
}
return;
}
else {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are not in a group.");
return;
}
}
else {
if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsBot()) {
Bot* bot = (Bot*)client->GetPlayer()->GetTarget();
Player* player = client->GetPlayer();
if (bot && bot->GetOwner() == player) {
bot->SetLocation(player->GetLocation());
bot->SetX(player->GetX());
bot->SetY(player->GetY());
bot->SetZ(player->GetZ());
client->Message(CHANNEL_COLOR_YELLOW, "Summoning %s.", bot->GetName());
return;
}
else {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only summon your own bots.");
return;
}
}
else {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot.");
return;
}
}
}
else if (strncasecmp("test", sep->arg[0], 4) == 0) {
if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsBot()) {
((Bot*)client->GetPlayer()->GetTarget())->MessageGroup("Test message");
return;
}
}
}
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "BotCommands:");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot create [race] [gender] [class] [name]");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot customize - customize the appearance of the bot");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot list - list all the bots you have created with this character");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot spawn [id] - spawns a bot into the world, id obtained from /bot list");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot inv [give/list/remove] - manage bot equipment, for remove a slot must be provided");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot settings [helm/hood/cloak/taunt] [0/1] - Turn setting on (1) or off(0)");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot camp - removes the bot from your group and despawns them");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot attack - commands your bots to attack your target");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot spells - lists bot spells, not fully implemented yet");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot maintank - sets targeted group member as the main tank for your bots");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot delete [id] - deletes the bot with the given id (obtained from /bot list)");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot help");
}
void Commands::Command_Bot_Create(Client* client, Seperator* sep) {
int8 race = BARBARIAN;
int8 gender = 0;
int8 advClass = GUARDIAN;
string name;
if (sep) {
if (sep->IsSet(0) && sep->IsNumber(0))
race = atoi(sep->arg[0]);
else {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "First param of \"/bot create\" needs to be a number");
return;
}
if (sep->IsSet(1) && sep->IsNumber(1))
gender = atoi(sep->arg[1]);
else {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Second param of \"/bot create\" needs to be a number");
return;
}
if (sep->IsSet(2) && sep->IsNumber(2))
advClass = atoi(sep->arg[2]);
else {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Third param of \"/bot create\" needs to be a number");
return;
}
if (sep->IsSet(3)) {
name = string(sep->arg[3]);
transform(name.begin(), name.begin() + 1, name.begin(), ::toupper);
transform(name.begin() + 1, name.end(), name.begin() + 1, ::tolower);
}
else {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Fourth param (name) of \"/bot create\" is required");
return;
}
}
else {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Syntax: /bot create [race ID] [Gender ID] [class ID] [name]");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "All parameters are required. /bot help race or /bot help class for ID's.");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Gender ID's: 0 = Female, 1 = Male");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Ex: /bot create 0 0 3 Botty");
return;
}
int8 result = database.CheckNameFilter(name.c_str());
if (result == BADNAMELENGTH_REPLY) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Name length is invalid, must be greater then 3 characters and less then 16.");
return;
}
else if (result == NAMEINVALID_REPLY) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Name is invalid, can only contain letters.");
return;
}
else if (result == NAMETAKEN_REPLY) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Name is already taken, please choose another.");
return;
}
else if (result == NAMEFILTER_REPLY) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Name failed the filter check.");
return;
}
else if (result == UNKNOWNERROR_REPLY) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Unknown error while checking the name.");
return;
}
string race_string;
switch (race) {
case BARBARIAN:
race_string = "/barbarian/barbarian";
break;
case DARK_ELF:
race_string = "/darkelf/darkelf";
break;
case DWARF:
race_string = "/dwarf/dwarf";
break;
case ERUDITE:
race_string = "/erudite/erudite";
break;
case FROGLOK:
race_string = "/froglok/froglok";
break;
case GNOME:
race_string = "/gnome/gnome";
break;
case HALF_ELF:
race_string = "/halfelf/halfelf";
break;
case HALFLING:
race_string = "/halfling/halfling";
break;
case HIGH_ELF:
race_string = "/highelf/highelf";
break;
case HUMAN:
race_string = "/human/human";
break;
case IKSAR:
race_string = "/iksar/iksar";
break;
case KERRA:
race_string = "/kerra/kerra";
break;
case OGRE:
race_string = "/ogre/ogre";
break;
case RATONGA:
race_string = "/ratonga/ratonga";
break;
case TROLL:
race_string = "/troll/troll";
break;
case WOOD_ELF:
race_string = "/woodelf/woodelf";
break;
case FAE:
race_string = "/fae/fae_light";
break;
case ARASAI:
race_string = "/fae/fae_dark";
break;
case SARNAK:
gender == 1 ? race_string = "01/sarnak_male/sarnak" : race_string = "01/sarnak_female/sarnak";
break;
case VAMPIRE:
race_string = "/vampire/vampire";
break;
case AERAKYN:
race_string = "/aerakyn/aerakyn";
break;
}
if (race_string.length() > 0) {
string gender_string;
Bot* bot = 0;
gender == 1 ? gender_string = "male" : gender_string = "female";
vector<int16>* id_list = database.GetAppearanceIDsLikeName("ec/pc" + race_string + "_" + gender_string);
if (id_list) {
bot = new Bot();
memset(&bot->appearance, 0, sizeof(bot->appearance));
bot->appearance.pos.collision_radius = 32;
bot->secondary_command_list_id = 0;
bot->primary_command_list_id = 0;
bot->appearance.display_name = 1;
bot->appearance.show_level = 1;
bot->appearance.attackable = 1;
bot->appearance.show_command_icon = 1;
bot->appearance.targetable = 1;
bot->appearance.race = race;
bot->appearance.gender = gender;
bot->SetID(Spawn::NextID());
bot->SetX(client->GetPlayer()->GetX());
bot->SetY(client->GetPlayer()->GetY());
bot->SetZ(client->GetPlayer()->GetZ());
bot->SetHeading(client->GetPlayer()->GetHeading());
bot->SetSpawnOrigX(bot->GetX());
bot->SetSpawnOrigY(bot->GetY());
bot->SetSpawnOrigZ(bot->GetZ());
bot->SetSpawnOrigHeading(bot->GetHeading());
bot->SetLocation(client->GetPlayer()->GetLocation());
bot->SetInitialState(16512);
bot->SetModelType(id_list->at(0));
bot->SetAdventureClass(advClass);
bot->SetLevel(client->GetPlayer()->GetLevel());
bot->SetName(name.c_str());
bot->SetDifficulty(6);
bot->size = 32;
if (bot->GetTotalHP() == 0) {
bot->SetTotalHP(25 * bot->GetLevel() + 1);
bot->SetTotalHPBaseInstance(bot->GetTotalHP());
bot->SetHP(25 * bot->GetLevel() + 1);
}
if (bot->GetTotalPower() == 0) {
bot->SetTotalPower(25 * bot->GetLevel() + 1);
bot->SetTotalPowerBaseInstance(bot->GetTotalPower());
bot->SetPower(25 * bot->GetLevel() + 1);
}
bot->SetOwner(client->GetPlayer());
bot->GetNewSpells();
client->GetCurrentZone()->AddSpawn(bot);
int32 index;
int32 bot_id = database.CreateNewBot(client->GetCharacterID(), name, race, advClass, gender, id_list->at(0), index);
if (bot_id == 0) {
LogWrite(PLAYER__ERROR, 0, "Player", "Error saving bot to DB. Bot was not saved!");
client->SimpleMessage(CHANNEL_ERROR, "Error saving bot to DB. Bot was not saved!");
}
else {
bot->BotID = bot_id;
bot->BotIndex = index;
client->GetPlayer()->SpawnedBots[bot->BotIndex] = bot->GetID();
// Add Items
database.SetBotStartingItems(bot, advClass, race);
}
}
else {
client->SimpleMessage(CHANNEL_COLOR_RED, "Error finding the id list for your race, please verify the race id.");
}
safe_delete(id_list);
}
else
client->SimpleMessage(CHANNEL_COLOR_RED, "Error finding the race string, please verify the race id.");
}
void Commands::Command_Bot_Customize(Client* client, Seperator* sep) {
Bot* bot = 0;
if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsBot())
bot = (Bot*)client->GetPlayer()->GetTarget();
client->Message(CHANNEL_COLOR_RED, "This command is disabled and requires new implementation.");
/*if (bot && bot->GetOwner() == client->GetPlayer()) {
PacketStruct* packet = configReader.getStruct("WS_OpenCharCust", client->GetVersion());
if (packet) {
AppearanceData* botApp = &bot->appearance;
CharFeatures* botFeatures = &bot->features;
AppearanceData* playerApp = &client->GetPlayer()->appearance;
CharFeatures* playerFeatures = &client->GetPlayer()->features;
memcpy(&client->GetPlayer()->SavedApp, playerApp, sizeof(AppearanceData));
memcpy(&client->GetPlayer()->SavedFeatures, playerFeatures, sizeof(CharFeatures));
client->GetPlayer()->custNPC = true;
client->GetPlayer()->custNPCTarget = bot;
memcpy(playerApp, botApp, sizeof(AppearanceData));
memcpy(playerFeatures, botFeatures, sizeof(CharFeatures));
client->GetPlayer()->changed = true;
client->GetPlayer()->info_changed = true;
client->GetCurrentZone()->SendSpawnChanges(client->GetPlayer(), client);
packet->setDataByName("race_id", 255);
client->QueuePacket(packet->serialize());
}
}*/
}
void Commands::Command_Bot_Spawn(Client* client, Seperator* sep) {
if (sep && sep->IsSet(0) && sep->IsNumber(0)) {
int32 bot_id = atoi(sep->arg[0]);
if (client->GetPlayer()->SpawnedBots.count(bot_id) > 0) {
client->Message(CHANNEL_COLOR_YELLOW, "The bot with id %u is already spawned.", bot_id);
return;
}
Bot* bot = new Bot();
memset(&bot->appearance, 0, sizeof(bot->appearance));
if (database.LoadBot(client->GetCharacterID(), bot_id, bot)) {
bot->SetFollowTarget(client->GetPlayer(), 5);
bot->appearance.pos.collision_radius = 32;
bot->secondary_command_list_id = 0;
bot->primary_command_list_id = 0;
bot->appearance.display_name = 1;
bot->appearance.show_level = 1;
bot->appearance.attackable = 1;
bot->appearance.show_command_icon = 1;
bot->appearance.targetable = 1;
bot->SetID(Spawn::NextID());
bot->SetX(client->GetPlayer()->GetX());
bot->SetY(client->GetPlayer()->GetY());
bot->SetZ(client->GetPlayer()->GetZ());
bot->SetHeading(client->GetPlayer()->GetHeading());
bot->SetSpawnOrigX(bot->GetX());
bot->SetSpawnOrigY(bot->GetY());
bot->SetSpawnOrigZ(bot->GetZ());
bot->SetSpawnOrigHeading(bot->GetHeading());
bot->SetLocation(client->GetPlayer()->GetLocation());
bot->SetInitialState(16512);
bot->SetLevel(client->GetPlayer()->GetLevel());
bot->SetDifficulty(6);
bot->size = 32;
if (bot->GetTotalHP() == 0) {
bot->SetTotalHP(25 * bot->GetLevel() + 1);
bot->SetHP(25 * bot->GetLevel() + 1);
}
if (bot->GetTotalPower() == 0) {
bot->SetTotalPower(25 * bot->GetLevel() + 1);
bot->SetPower(25 * bot->GetLevel() + 1);
}
bot->SetOwner(client->GetPlayer());
bot->UpdateWeapons();
bot->CalculateBonuses();
bot->GetNewSpells();
client->GetCurrentZone()->AddSpawn(bot);
if (sep->IsSet(1) && sep->IsNumber(1) && atoi(sep->arg[1]) == 1) {
client->GetCurrentZone()->SendSpawn(bot, client);
int8 result = world.GetGroupManager()->Invite(client->GetPlayer(), bot);
if (result == 0)
client->Message(CHANNEL_COMMANDS, "You invite %s to group with you.", bot->GetName());
else if (result == 1)
client->SimpleMessage(CHANNEL_COMMANDS, "That player is already in a group.");
else if (result == 2)
client->SimpleMessage(CHANNEL_COMMANDS, "That player has been invited to another group.");
else if (result == 3)
client->SimpleMessage(CHANNEL_COMMANDS, "Your group is already full.");
else if (result == 4)
client->SimpleMessage(CHANNEL_COMMANDS, "You have a pending invitation, cancel it first.");
else if (result == 5)
client->SimpleMessage(CHANNEL_COMMANDS, "You cannot invite yourself!");
else if (result == 6)
client->SimpleMessage(CHANNEL_COMMANDS, "Could not locate the player.");
else
client->SimpleMessage(CHANNEL_COMMANDS, "Group invite failed, unknown error!");
}
client->GetPlayer()->SpawnedBots[bot_id] = bot->GetID();
if(bot->IsNPC()) {
((NPC*)bot)->HaltMovement();
}
}
else {
client->Message(CHANNEL_ERROR, "Error spawning bot (%u)", bot_id);
}
}
else {
Command_Bot(client, sep);
}
}
void Commands::Command_Bot_List(Client* client, Seperator* sep) {
string bot_list;
bot_list = database.GetBotList(client->GetCharacterID());
if (!bot_list.empty())
client->SimpleMessage(CHANNEL_COLOR_YELLOW, bot_list.c_str());
}
void Commands::Command_Bot_Inv(Client* client, Seperator* sep) {
if (sep && sep->IsSet(0)) {
if (strncasecmp("give", sep->arg[0], 4) == 0) {
if (client->GetPlayer()->trade) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are already trading.");
return;
}
if (!client->GetPlayer()->GetTarget() || !client->GetPlayer()->GetTarget()->IsBot()) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot");
return;
}
Bot* bot = (Bot*)client->GetPlayer()->GetTarget();
if (bot->trade) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Bot is already in a trade...");
return;
}
if (bot->GetOwner() != client->GetPlayer()) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only trade with your own bot.");
return;
}
Trade* trade = new Trade(client->GetPlayer(), bot);
client->GetPlayer()->trade = trade;
bot->trade = trade;
}
else if (strncasecmp("list", sep->arg[0], 4) == 0) {
if (!client->GetPlayer()->GetTarget() || !client->GetPlayer()->GetTarget()->IsBot()) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot");
return;
}
Bot* bot = (Bot*)client->GetPlayer()->GetTarget();
if (bot->GetOwner() != client->GetPlayer()) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only see the inventory of your own bot.");
return;
}
string item_list = "Bot Items:\nSlot\tName\n";
for (int8 i = 0; i < NUM_SLOTS; i++) {
Item* item = bot->GetEquipmentList()->GetItem(i);
if (item) {
//\\aITEM %u %u:%s\\/a
item_list += to_string(i) + ":\t" + item->CreateItemLink(client->GetVersion(), true) + "\n";
}
}
client->SimpleMessage(CHANNEL_COLOR_YELLOW, item_list.c_str());
}
else if (strncasecmp("remove", sep->arg[0], 6) == 0) {
if (sep->IsSet(1) && sep->IsNumber(1)) {
int8 slot = atoi(sep->arg[1]);
if (slot >= NUM_SLOTS) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Invalid slot");
return;
}
if (!client->GetPlayer()->GetTarget() || !client->GetPlayer()->GetTarget()->IsBot()) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot.");
return;
}
Bot* bot = (Bot*)client->GetPlayer()->GetTarget();
if (bot->GetOwner() != client->GetPlayer()) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only remove items from your own bot.");
return;
}
if (client->GetPlayer()->trade) {
Trade* trade = client->GetPlayer()->trade;
if (trade->GetTradee(client->GetPlayer()) != bot) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You are already in a trade.");
return;
}
bot->AddItemToTrade(slot);
}
else {
if (bot->trade) {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Your bot is already trading...");
return;
}
Trade* trade = new Trade(client->GetPlayer(), bot);
client->GetPlayer()->trade = trade;
bot->trade = trade;
bot->AddItemToTrade(slot);
}
}
}
else
Command_Bot(client, sep);
}
else
Command_Bot(client, sep);
}
void Commands::Command_Bot_Settings(Client* client, Seperator* sep) {
if (sep && sep->IsSet(0) && sep->IsSet(1) && sep->IsNumber(1)) {
if (client->GetPlayer()->GetTarget() && client->GetPlayer()->GetTarget()->IsBot()) {
Bot* bot = (Bot*)client->GetPlayer()->GetTarget();
if (bot->GetOwner() == client->GetPlayer()) {
if (strncasecmp("helm", sep->arg[0], 4) == 0) {
bot->ShowHelm = (atoi(sep->arg[1]) == 1) ? true : false;
bot->info_changed = true;
bot->changed = true;
bot->GetZone()->SendSpawnChanges(bot);
}
else if (strncasecmp("cloak", sep->arg[0], 5) == 0) {
bot->ShowCloak = (atoi(sep->arg[1]) == 1) ? true : false;
bot->info_changed = true;
bot->changed = true;
bot->GetZone()->SendSpawnChanges(bot);
}
else if (strncasecmp("taunt", sep->arg[0], 5) == 0) {
bot->CanTaunt = (atoi(sep->arg[1]) == 1) ? true : false;
}
else if (strncasecmp("hood", sep->arg[0], 4) == 0) {
bot->SetHideHood((atoi(sep->arg[0]) == 1) ? 0 : 1);
}
else
Command_Bot(client, sep);
}
else
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You can only change settings on your own bot.");
}
else
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "You must target a bot.");
}
else
Command_Bot(client, sep);
}
void Commands::Command_Bot_Help(Client* client, Seperator* sep) {
if (sep && sep->IsSet(0)) {
if (strncasecmp("race", sep->arg[0], 4) == 0) {
string title = "Race ID's";
string details;
details += "0\tBarbarian\n";
details += "1\tDark Elf\n";
details += "2\tDwarf\n";
details += "3\tErudite\n";
details += "4\tFroglok\n";
details += "5\tGnome\n";
details += "6\tHalf Elf\n";
details += "7\tHalfling\n";
details += "8\tHigh Elf\n";
details += "9\tHuman\n";
details += "10\tIksar\n";
details += "11\tKerra\n";
details += "12\tOgre\n";
details += "13\tRatonga\n";
details += "14\tTroll\n";
details += "15\tWood Elf\n";
details += "16\tFae\n";
details += "17\tArasai\n";
details += "18\tSarnak\n";
details += "19\tVampire\n";
details += "20\tAerakyn\n";
client->SendShowBook(client->GetPlayer(), title, 0, 1, details);
return;
}
else if (strncasecmp("class", sep->arg[0], 5) == 0) {
string title = "Class ID's";
string details;
details += "0\tCOMMONER\n";
details += "1\tFIGHTER\n";
details += "2\tWARRIOR\n";
details += "3\tGUARDIAN\n";
details += "4\tBERSERKER\n";
details += "5\tBRAWLER\n";
details += "6\tMONK\n";
details += "7\tBRUISER\n";
details += "8\tCRUSADER\n";
details += "9\tSHADOWKNIGHT\n";
details += "10\tPALADIN\n";
details += "11\tPRIEST\n";
details += "12\tCLERIC\n";
details += "13\tTEMPLAR\n";
details += "14\tINQUISITOR\n";
details += "15\tDRUID\n";
details += "16\tWARDEN\n";
details += "17\tFURY\n";
details += "18\tSHAMAN\n";
details += "19\tMYSTIC\n";
details += "20\tDEFILER\n";
string details2 = "21\tMAGE\n";
details2 += "22\tSORCERER\n";
details2 += "23\tWIZARD\n";
details2 += "24\tWARLOCK\n";
details2 += "25\tENCHANTER\n";
details2 += "26\tILLUSIONIST\n";
details2 += "27\tCOERCER\n";
details2 += "28\tSUMMONER\n";
details2 += "29\tCONJUROR\n";
details2 += "30\tNECROMANCER\n";
details2 += "31\tSCOUT\n";
details2 += "32\tROGUE\n";
details2 += "33\tSWASHBUCKLER\n";
details2 += "34\tBRIGAND\n";
details2 += "35\tBARD\n";
details2 += "36\tTROUBADOR\n";
details2 += "37\tDIRGE\n";
details2 += "38\tPREDATOR\n";
details2 += "39\tRANGER\n";
details2 += "40\tASSASSIN\n";
string details3 = "\\#FF0000Following aren't implemented yet.\\#000000\n";
details3 += "41\tANIMALIST\n";
details3 += "42\tBEASTLORD\n";
details3 += "43\tSHAPER\n";
details3 += "44\tCHANNELER\n";
client->SendShowBook(client->GetPlayer(), title, 0, 3, details, details2, details3);
return;
}
}
else {
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Bot help is WIP.");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot help race - race id list");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "/bot help class - class id list");
}
}

View File

@ -0,0 +1,467 @@
#include "../Worlddatabase.hpp"
#include "../../common/Log.h"
#include "Bot.h"
#include "../classes.h"
#include "../races.h"
extern Classes classes;
extern Races races;
int32 WorldDatabase::CreateNewBot(int32 char_id, string name, int8 race, int8 advClass, int8 gender, int16 model_id, int32& index) {
DatabaseResult result;
index = 0;
if (!database_new.Select(&result, "SELECT MAX(`bot_id`) FROM `bots` WHERE `char_id` = %u", char_id)) {
LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg());
return 0;
}
if (result.Next()) {
if (result.IsNull(0))
index = 1;
else
index = result.GetInt32(0) + 1;
}
if (!database_new.Query("INSERT INTO `bots` (`char_id`, `bot_id`, `name`, `race`, `class`, `gender`, `model_type`) VALUES (%u, %u, \"%s\", %u, %u, %u, %u)", char_id, index, name.c_str(), race, advClass, gender, model_id)) {
LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg());
return 0;
}
int32 ret = database_new.LastInsertID();
LogWrite(PLAYER__DEBUG, 0, "Player", "New bot (%s) created for player (%u)", name.c_str(), char_id);
return ret;
}
void WorldDatabase::SaveBotAppearance(Bot* bot) {
SaveBotColors(bot->BotID, "skin_color", bot->features.skin_color);
SaveBotColors(bot->BotID, "model_color", bot->features.model_color);
SaveBotColors(bot->BotID, "eye_color", bot->features.eye_color);
SaveBotColors(bot->BotID, "hair_color1", bot->features.hair_color1);
SaveBotColors(bot->BotID, "hair_color2", bot->features.hair_color2);
SaveBotColors(bot->BotID, "hair_highlight", bot->features.hair_highlight_color);
SaveBotColors(bot->BotID, "hair_type_color", bot->features.hair_type_color);
SaveBotColors(bot->BotID, "hair_type_highlight_color", bot->features.hair_type_highlight_color);
SaveBotColors(bot->BotID, "hair_face_color", bot->features.hair_face_color);
SaveBotColors(bot->BotID, "hair_face_highlight_color", bot->features.hair_face_highlight_color);
SaveBotColors(bot->BotID, "wing_color1", bot->features.wing_color1);
SaveBotColors(bot->BotID, "wing_color2", bot->features.wing_color2);
SaveBotColors(bot->BotID, "shirt_color", bot->features.shirt_color);
//SaveBotColors(bot->BotID, "unknown_chest_color", );
SaveBotColors(bot->BotID, "pants_color", bot->features.pants_color);
//SaveBotColors(bot->BotID, "unknown_legs_color", );
//SaveBotColors(bot->BotID, "unknown9", );
SaveBotFloats(bot->BotID, "eye_type", bot->features.eye_type[0], bot->features.eye_type[1], bot->features.eye_type[2]);
SaveBotFloats(bot->BotID, "ear_type", bot->features.ear_type[0], bot->features.ear_type[1], bot->features.ear_type[2]);
SaveBotFloats(bot->BotID, "eye_brow_type", bot->features.eye_brow_type[0], bot->features.eye_brow_type[1], bot->features.eye_brow_type[2]);
SaveBotFloats(bot->BotID, "cheek_type", bot->features.cheek_type[0], bot->features.cheek_type[1], bot->features.cheek_type[2]);
SaveBotFloats(bot->BotID, "lip_type", bot->features.lip_type[0], bot->features.lip_type[1], bot->features.lip_type[2]);
SaveBotFloats(bot->BotID, "chin_type", bot->features.chin_type[0], bot->features.chin_type[1], bot->features.chin_type[2]);
SaveBotFloats(bot->BotID, "nose_type", bot->features.nose_type[0], bot->features.nose_type[1], bot->features.nose_type[2]);
SaveBotFloats(bot->BotID, "body_size", bot->features.body_size, 0, 0);
SaveBotFloats(bot->BotID, "body_age", bot->features.body_age, 0, 0);
SaveBotColors(bot->BotID, "soga_skin_color", bot->features.soga_skin_color);
SaveBotColors(bot->BotID, "soga_model_color", bot->features.soga_model_color);
SaveBotColors(bot->BotID, "soga_eye_color", bot->features.soga_eye_color);
SaveBotColors(bot->BotID, "soga_hair_color1", bot->features.soga_hair_color1);
SaveBotColors(bot->BotID, "soga_hair_color2", bot->features.soga_hair_color2);
SaveBotColors(bot->BotID, "soga_hair_highlight", bot->features.soga_hair_highlight_color);
SaveBotColors(bot->BotID, "soga_hair_type_color", bot->features.soga_hair_type_color);
SaveBotColors(bot->BotID, "soga_hair_type_highlight_color", bot->features.soga_hair_type_highlight_color);
SaveBotColors(bot->BotID, "soga_hair_face_color", bot->features.soga_hair_face_color);
SaveBotColors(bot->BotID, "soga_hair_face_highlight_color", bot->features.soga_hair_face_highlight_color);
SaveBotColors(bot->BotID, "soga_wing_color1", bot->features.wing_color1);
SaveBotColors(bot->BotID, "soga_wing_color2", bot->features.wing_color2);
SaveBotColors(bot->BotID, "soga_shirt_color", bot->features.shirt_color);
//SaveBotColors(bot->BotID, "soga_unknown_chest_color", );
SaveBotColors(bot->BotID, "soga_pants_color", bot->features.pants_color);
//SaveBotColors(bot->BotID, "soga_unknown_legs_color", );
//SaveBotColors(bot->BotID, "soga_unknown13", );
SaveBotFloats(bot->BotID, "soga_eye_type", bot->features.soga_eye_type[0], bot->features.soga_eye_type[1], bot->features.soga_eye_type[2]);
SaveBotFloats(bot->BotID, "soga_ear_type", bot->features.soga_ear_type[0], bot->features.soga_ear_type[1], bot->features.soga_ear_type[2]);
SaveBotFloats(bot->BotID, "soga_eye_brow_type", bot->features.soga_eye_brow_type[0], bot->features.soga_eye_brow_type[1], bot->features.soga_eye_brow_type[2]);
SaveBotFloats(bot->BotID, "soga_cheek_type", bot->features.soga_cheek_type[0], bot->features.soga_cheek_type[1], bot->features.soga_cheek_type[2]);
SaveBotFloats(bot->BotID, "soga_lip_type", bot->features.soga_lip_type[0], bot->features.soga_lip_type[1], bot->features.soga_lip_type[2]);
SaveBotFloats(bot->BotID, "soga_chin_type", bot->features.soga_chin_type[0], bot->features.soga_chin_type[1], bot->features.soga_chin_type[2]);
SaveBotFloats(bot->BotID, "soga_nose_type", bot->features.soga_nose_type[0], bot->features.soga_nose_type[1], bot->features.soga_nose_type[2]);
if (!database_new.Query("UPDATE `bots` SET `model_type` = %u, `hair_type` = %u, `face_type` = %u, `wing_type` = %u, `chest_type` = %u, `legs_type` = %u, `soga_model_type` = %u, `soga_hair_type` = %u, `soga_face_type` = %u WHERE `id` = %u",
bot->GetModelType(), bot->GetHairType(), bot->GetFacialHairType(), bot->GetWingType(), bot->GetChestType(), bot->GetLegsType(), bot->GetSogaModelType(), bot->GetSogaHairType(), bot->GetSogaFacialHairType(), bot->BotID)) {
LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg());
return;
}
}
void WorldDatabase::SaveBotColors(int32 bot_id, const char* type, EQ2_Color color) {
if (!database_new.Query("INSERT INTO `bot_appearance` (`bot_id`, `type`, `red`, `green`, `blue`) VALUES (%i, '%s', %i, %i, %i) ON DUPLICATE KEY UPDATE `red` = %i, `blue` = %i, `green` = %i", bot_id, type, color.red, color.green, color.blue, color.red, color.blue, color.green)) {
LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg());
return;
}
}
void WorldDatabase::SaveBotFloats(int32 bot_id, const char* type, float float1, float float2, float float3) {
if (!database_new.Query("INSERT INTO `bot_appearance` (`bot_id`, `type`, `red`, `green`, `blue`, `signed_value`) VALUES (%i, '%s', %i, %i, %i, 1) ON DUPLICATE KEY UPDATE `red` = %i, `blue` = %i, `green` = %i", bot_id, type, float1, float2, float3, float1, float2, float3)) {
LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg());
return;
}
}
bool WorldDatabase::LoadBot(int32 char_id, int32 bot_index, Bot* bot) {
DatabaseResult result;
if (!database_new.Select(&result, "SELECT * FROM bots WHERE `char_id` = %u AND `bot_id` = %u", char_id, bot_index)) {
LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg());
return false;
}
if (result.Next()) {
bot->BotID = result.GetInt32(0);
bot->BotIndex = result.GetInt32(2);
bot->SetName(result.GetString(3));
bot->SetRace(result.GetInt8(4));
bot->SetAdventureClass(result.GetInt8(5));
bot->SetGender(result.GetInt8(6));
bot->SetModelType(result.GetInt16(7));
bot->SetHairType(result.GetInt16(8));
bot->SetFacialHairType(result.GetInt16(9));
bot->SetWingType(result.GetInt16(10));
bot->SetChestType(result.GetInt16(11));
bot->SetLegsType(result.GetInt16(12));
bot->SetSogaModelType(result.GetInt16(13));
bot->SetSogaHairType(result.GetInt16(14));
bot->SetSogaFacialHairType(result.GetInt16(15));
}
else
return false;
LoadBotAppearance(bot);
LoadBotEquipment(bot);
return true;
}
void WorldDatabase::LoadBotAppearance(Bot* bot) {
DatabaseResult result;
string type;
map<string, int8> appearance_types;
EQ2_Color color;
color.red = 0;
color.green = 0;
color.blue = 0;
if (!database_new.Select(&result, "SELECT distinct `type` FROM bot_appearance WHERE length(`type`) > 0 AND `bot_id` = %u", bot->BotID)) {
LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg());
return;
}
while (result.Next()) {
type = result.GetString(0);
appearance_types[type] = GetAppearanceType(type);
if (appearance_types[type] == 255)
LogWrite(WORLD__ERROR, 0, "Appearance", "Unknown appearance type '%s' in LoadBotAppearances.", type.c_str());
}
if (!database_new.Select(&result, "SELECT `type`, `signed_value`, `red`, `green`, `blue` FROM bot_appearance WHERE length(`type`) > 0 AND bot_id = %u", bot->BotID)) {
LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg());
return;
}
while (result.Next()) {
type = result.GetString(0);
if (appearance_types[type] < APPEARANCE_SOGA_EBT) {
color.red = result.GetInt8(2);
color.green = result.GetInt8(3);
color.blue = result.GetInt8(4);
}
switch (appearance_types[type]) {
case APPEARANCE_SOGA_HFHC: {
bot->features.soga_hair_face_highlight_color = color;
break;
}
case APPEARANCE_SOGA_HTHC: {
bot->features.soga_hair_type_highlight_color = color;
break;
}
case APPEARANCE_SOGA_HFC: {
bot->features.soga_hair_face_color = color;
break;
}
case APPEARANCE_SOGA_HTC: {
bot->features.soga_hair_type_color = color;
break;
}
case APPEARANCE_SOGA_HH: {
bot->features.soga_hair_highlight_color = color;
break;
}
case APPEARANCE_SOGA_HC1: {
bot->features.soga_hair_color1 = color;
break;
}
case APPEARANCE_SOGA_HC2: {
bot->features.soga_hair_color2 = color;
break;
}
case APPEARANCE_SOGA_SC: {
bot->features.soga_skin_color = color;
break;
}
case APPEARANCE_SOGA_EC: {
bot->features.soga_eye_color = color;
break;
}
case APPEARANCE_HTHC: {
bot->features.hair_type_highlight_color = color;
break;
}
case APPEARANCE_HFHC: {
bot->features.hair_face_highlight_color = color;
break;
}
case APPEARANCE_HTC: {
bot->features.hair_type_color = color;
break;
}
case APPEARANCE_HFC: {
bot->features.hair_face_color = color;
break;
}
case APPEARANCE_HH: {
bot->features.hair_highlight_color = color;
break;
}
case APPEARANCE_HC1: {
bot->features.hair_color1 = color;
break;
}
case APPEARANCE_HC2: {
bot->features.hair_color2 = color;
break;
}
case APPEARANCE_WC1: {
bot->features.wing_color1 = color;
break;
}
case APPEARANCE_WC2: {
bot->features.wing_color2 = color;
break;
}
case APPEARANCE_SC: {
bot->features.skin_color = color;
break;
}
case APPEARANCE_EC: {
bot->features.eye_color = color;
break;
}
case APPEARANCE_SOGA_EBT: {
for (int i = 0; i < 3; i++)
bot->features.soga_eye_brow_type[i] = result.GetSInt8(2 + i);
break;
}
case APPEARANCE_SOGA_CHEEKT: {
for (int i = 0; i < 3; i++)
bot->features.soga_cheek_type[i] = result.GetSInt8(2 + i);
break;
}
case APPEARANCE_SOGA_NT: {
for (int i = 0; i < 3; i++)
bot->features.soga_nose_type[i] = result.GetSInt8(2 + i);
break;
}
case APPEARANCE_SOGA_CHINT: {
for (int i = 0; i < 3; i++)
bot->features.soga_chin_type[i] = result.GetSInt8(2 + i);
break;
}
case APPEARANCE_SOGA_LT: {
for (int i = 0; i < 3; i++)
bot->features.soga_lip_type[i] = result.GetSInt8(2 + i);
break;
}
case APPEARANCE_SOGA_EART: {
for (int i = 0; i < 3; i++)
bot->features.soga_ear_type[i] = result.GetSInt8(2 + i);
break;
}
case APPEARANCE_SOGA_EYET: {
for (int i = 0; i < 3; i++)
bot->features.soga_eye_type[i] = result.GetSInt8(2 + i);
break;
}
case APPEARANCE_EBT: {
for (int i = 0; i < 3; i++)
bot->features.eye_brow_type[i] = result.GetSInt8(2 + i);
break;
}
case APPEARANCE_CHEEKT: {
for (int i = 0; i < 3; i++)
bot->features.cheek_type[i] = result.GetSInt8(2 + i);
break;
}
case APPEARANCE_NT: {
for (int i = 0; i < 3; i++)
bot->features.nose_type[i] = result.GetSInt8(2 + i);
break;
}
case APPEARANCE_CHINT: {
for (int i = 0; i < 3; i++)
bot->features.chin_type[i] = result.GetSInt8(2 + i);
break;
}
case APPEARANCE_EART: {
for (int i = 0; i < 3; i++)
bot->features.ear_type[i] = result.GetSInt8(2 + i);
break;
}
case APPEARANCE_EYET: {
for (int i = 0; i < 3; i++)
bot->features.eye_type[i] = result.GetSInt8(2 + i);
break;
}
case APPEARANCE_LT: {
for (int i = 0; i < 3; i++)
bot->features.lip_type[i] = result.GetSInt8(2 + i);
break;
}
case APPEARANCE_SHIRT: {
bot->features.shirt_color = color;
break;
}
case APPEARANCE_UCC: {
break;
}
case APPEARANCE_PANTS: {
bot->features.pants_color = color;
break;
}
case APPEARANCE_ULC: {
break;
}
case APPEARANCE_U9: {
break;
}
case APPEARANCE_BODY_SIZE: {
bot->features.body_size = color.red;
break;
}
case APPEARANCE_SOGA_WC1: {
break;
}
case APPEARANCE_SOGA_WC2: {
break;
}
case APPEARANCE_SOGA_SHIRT: {
break;
}
case APPEARANCE_SOGA_UCC: {
break;
}
case APPEARANCE_SOGA_PANTS: {
break;
}
case APPEARANCE_SOGA_ULC: {
break;
}
case APPEARANCE_SOGA_U13: {
break;
}
case APPEARANCE_BODY_AGE: {
bot->features.body_age = color.red;
break;
}
case APPEARANCE_MC:{
bot->features.model_color = color;
break;
}
case APPEARANCE_SMC:{
bot->features.soga_model_color = color;
break;
}
case APPEARANCE_SBS: {
bot->features.soga_body_size = color.red;
break;
}
case APPEARANCE_SBA: {
bot->features.soga_body_age = color.red;
break;
}
}
}
}
void WorldDatabase::SaveBotItem(int32 bot_id, int32 item_id, int8 slot) {
if (!database_new.Query("INSERT INTO `bot_equipment` (`bot_id`, `slot`, `item_id`) VALUES (%u, %u, %u) ON DUPLICATE KEY UPDATE `item_id` = %u", bot_id, slot, item_id, item_id)) {
LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg());
return;
}
}
void WorldDatabase::LoadBotEquipment(Bot* bot) {
DatabaseResult result;
if (!database_new.Select(&result, "SELECT `slot`, `item_id` FROM `bot_equipment` WHERE `bot_id` = %u", bot->BotID)) {
LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg());
return;
}
Item* master_item = 0;
Item* item = 0;
while (result.Next()) {
int8 slot = result.GetInt8(0);
int32 item_id = result.GetInt32(1);
master_item = master_item_list.GetItem(item_id);
if (master_item) {
item = new Item(master_item);
if (item) {
bot->GetEquipmentList()->AddItem(slot, item);
bot->SetEquipment(item, slot);
}
}
}
}
string WorldDatabase::GetBotList(int32 char_id) {
DatabaseResult result;
string ret;
if (!database_new.Select(&result, "SELECT `bot_id`, `name`, `race`, `class` FROM `bots` WHERE `char_id` = %u", char_id)) {
LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg());
return ret;
}
while (result.Next()) {
ret += to_string(result.GetInt32(0)) + ": ";
ret += result.GetString(1);
ret += " the ";
ret += races.GetRaceNameCase(result.GetInt8(2));
ret += " ";
ret += classes.GetClassNameCase(result.GetInt8(3)) + "\n";
}
return ret;
}
void WorldDatabase::DeleteBot(int32 char_id, int32 bot_index) {
if (!database_new.Query("DELETE FROM `bots` WHERE `char_id` = %u AND `bot_id` = %u", char_id, bot_index)) {
LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg());
}
}
void WorldDatabase::SetBotStartingItems(Bot* bot, int8 class_id, int8 race_id) {
int32 bot_id = bot->BotID;
LogWrite(PLAYER__DEBUG, 0, "Bot", "Adding default items for race: %u, class: %u for bot_id: %u", race_id, class_id, bot_id);
DatabaseResult result;
if (!database_new.Select(&result, "SELECT item_id FROM starting_items WHERE class_id IN (%i, %i, %i, 255) AND race_id IN (%i, 255) ORDER BY id", classes.GetBaseClass(class_id), classes.GetSecondaryBaseClass(class_id), class_id, race_id)) {
LogWrite(DATABASE__ERROR, 0, "DBNew", "MySQL Error %u: %s", database_new.GetError(), database_new.GetErrorMsg());
return;
}
while (result.Next()) {
bot->GiveItem(result.GetInt32(0));
}
}

View File

@ -0,0 +1,372 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Chat.h"
#include "../../common/Log.h"
#include "../../common/config_reader.hpp"
#include "../../common/PacketStruct.h"
#include "../Rules/Rules.h"
extern RuleManager rule_manager;
//devn00b
#ifdef DISCORD
#ifndef WIN32
#include <dpp/dpp.h>
#include "ChatChannel.h"
extern ChatChannel channel;
#endif
#endif
extern ConfigReader configReader;
Chat::Chat() {
m_channels.SetName("Chat::Channels");
}
Chat::~Chat() {
vector<ChatChannel *>::iterator itr;
m_channels.writelock(__FUNCTION__, __LINE__);
for (itr = channels.begin(); itr != channels.end(); itr++)
safe_delete(*itr);
m_channels.releasewritelock(__FUNCTION__, __LINE__);
}
void Chat::AddChannel(ChatChannel *channel) {
m_channels.writelock(__FUNCTION__, __LINE__);
channels.push_back(channel);
m_channels.releasewritelock(__FUNCTION__, __LINE__);
}
unsigned int Chat::GetNumChannels() {
unsigned int ret;
m_channels.readlock(__FUNCTION__, __LINE__);
ret = (unsigned int)channels.size();
m_channels.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
EQ2Packet * Chat::GetWorldChannelList(Client *client) {
PacketStruct *packet_struct = configReader.getStruct("WS_AvailWorldChannels", client->GetVersion());
Player *player = client->GetPlayer();
vector<ChatChannel *> channels_to_send;
vector<ChatChannel *>::iterator itr;
ChatChannel *channel;
EQ2Packet *packet;
int32 i = 0;
bool add;
if (packet_struct == NULL) {
LogWrite(CHAT__ERROR, 0, "Chat", "Could not find packet 'WS_AvailWorldChannels' for client %s on version %i\n", player->GetName(), client->GetVersion());
return NULL;
}
m_channels.readlock(__FUNCTION__, __LINE__);
for (itr = channels.begin(); itr != channels.end(); itr++) {
channel = *itr;
if (channel->GetType() == CHAT_CHANNEL_TYPE_WORLD) {
add = true;
if (add && !channel->CanJoinChannelByLevel(player->GetLevel()))
add = false;
if (add && !channel->CanJoinChannelByRace(player->GetRace()))
add = false;
if (add && !channel->CanJoinChannelByClass(player->GetAdventureClass()))
add = false;
if (add)
channels_to_send.push_back(channel);
}
}
m_channels.releasereadlock(__FUNCTION__, __LINE__);
packet_struct->setArrayLengthByName("num_channels", channels_to_send.size());
for (itr = channels_to_send.begin(); itr != channels_to_send.end(); itr++, i++) {
packet_struct->setArrayDataByName("channel_name", (*itr)->GetName(), i);
packet_struct->setArrayDataByName("unknown", 0, i);
}
packet = packet_struct->serialize();
safe_delete(packet_struct);
return packet;
}
bool Chat::ChannelExists(const char *channel_name) {
vector<ChatChannel *>::iterator itr;
bool ret = false;
m_channels.readlock(__FUNCTION__, __LINE__);
for (itr = channels.begin(); itr != channels.end(); itr++) {
if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) {
ret = true;
break;
}
}
m_channels.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
bool Chat::HasPassword(const char *channel_name) {
vector<ChatChannel *>::iterator itr;
bool ret = false;
m_channels.readlock(__FUNCTION__, __LINE__);
for (itr = channels.begin(); itr != channels.end(); itr++) {
if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) {
ret = (*itr)->HasPassword();
break;
}
}
m_channels.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
bool Chat::PasswordMatches(const char *channel_name, const char *password) {
vector<ChatChannel *>::iterator itr;
bool ret = false;
m_channels.readlock(__FUNCTION__, __LINE__);
for (itr = channels.begin(); itr != channels.end(); itr++) {
if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) {
ret = (*itr)->PasswordMatches(password);
break;
}
}
m_channels.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
bool Chat::CreateChannel(const char *channel_name) {
return CreateChannel(channel_name, NULL);
}
bool Chat::CreateChannel(const char *channel_name, const char *password) {
LogWrite(CHAT__DEBUG, 0, "Chat", "Channel %s being created", channel_name);
ChatChannel *channel = new ChatChannel();
channel->SetName(channel_name);
channel->SetType(CHAT_CHANNEL_TYPE_CUSTOM);
if (password != NULL)
channel->SetPassword(password);
m_channels.writelock(__FUNCTION__, __LINE__);
channels.push_back(channel);
m_channels.releasewritelock(__FUNCTION__, __LINE__);
return true;
}
bool Chat::IsInChannel(Client *client, const char *channel_name) {
vector<ChatChannel *>::iterator itr;
bool ret = false;
m_channels.readlock(__FUNCTION__, __LINE__);
for (itr = channels.begin(); itr != channels.end(); itr++) {
if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) {
ret = (*itr)->IsInChannel(client->GetCharacterID());
break;
}
}
m_channels.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
bool Chat::JoinChannel(Client *client, const char *channel_name) {
vector<ChatChannel *>::iterator itr;
bool ret = false;
LogWrite(CHAT__DEBUG, 1, "Chat", "Client %s is joining channel %s", client->GetPlayer()->GetName(), channel_name);
m_channels.writelock(__FUNCTION__, __LINE__);
for (itr = channels.begin(); itr != channels.end(); itr++) {
if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) {
ret = (*itr)->JoinChannel(client);
break;
}
}
m_channels.releasewritelock(__FUNCTION__, __LINE__);
return ret;
}
bool Chat::LeaveChannel(Client *client, const char *channel_name) {
vector<ChatChannel *>::iterator itr;
bool ret = false;
LogWrite(CHAT__DEBUG, 1, "Chat", "Client %s is leaving channel %s", client->GetPlayer()->GetName(), channel_name);
m_channels.writelock(__FUNCTION__, __LINE__);
for (itr = channels.begin(); itr != channels.end(); itr++) {
if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) {
ret = (*itr)->LeaveChannel(client);
if ((*itr)->GetType() == CHAT_CHANNEL_TYPE_CUSTOM && (*itr)->GetNumClients() == 0) {
LogWrite(CHAT__DEBUG, 0, "Chat", "Custom channel %s has 0 clients left, deleting channel", channel_name);
safe_delete(*itr);
channels.erase(itr);
}
break;
}
}
m_channels.releasewritelock(__FUNCTION__, __LINE__);
return ret;
}
bool Chat::LeaveAllChannels(Client *client) {
vector<ChatChannel *>::iterator itr;
ChatChannel *channel;
bool erased;
m_channels.writelock(__FUNCTION__, __LINE__);
itr = channels.begin();
while (itr != channels.end()) {
channel = *itr;
erased = false;
if (channel->IsInChannel(client->GetCharacterID())) {
LogWrite(CHAT__DEBUG, 1, "Chat", "Client %s is leaving channel %s", client->GetPlayer()->GetName(), channel->GetName());
channel->LeaveChannel(client);
if (channel->GetType() == CHAT_CHANNEL_TYPE_CUSTOM && channel->GetNumClients() == 0) {
LogWrite(CHAT__DEBUG, 0, "Chat", "Custom channel %s has 0 clients left, deleting channel", channel->GetName());
safe_delete(*itr);
itr = channels.erase(itr);
erased = true;
}
}
if (!erased)
itr++;
}
m_channels.releasewritelock(__FUNCTION__, __LINE__);
return true;
}
bool Chat::TellChannel(Client *client, const char *channel_name, const char *message, const char* name) {
vector<ChatChannel *>::iterator itr;
bool ret = false;
bool enablediscord = rule_manager.GetGlobalRule(R_Discord, DiscordEnabled)->GetBool();
const char* discordchan = rule_manager.GetGlobalRule(R_Discord, DiscordChannel)->GetString();
m_channels.readlock(__FUNCTION__, __LINE__);
for (itr = channels.begin(); itr != channels.end(); itr++) {
if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) {
if (client && name)
ret = (*itr)->TellChannelClient(client, message, name);
else
ret = (*itr)->TellChannel(client, message, name);
if(enablediscord == true && client){
if (strcmp(channel_name, discordchan) != 0){
m_channels.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
#ifdef DISCORD
if (client) {
std::string whofrom = client->GetPlayer()->GetName();
std::string msg = string(message);
ret = PushDiscordMsg(msg.c_str(), whofrom.c_str());
}
#endif
}
break;
}
}
m_channels.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
bool Chat::SendChannelUserList(Client *client, const char *channel_name) {
vector<ChatChannel *>::iterator itr;
bool ret = false;
m_channels.readlock(__FUNCTION__, __LINE__);
for (itr = channels.begin(); itr != channels.end(); itr++) {
if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) {
ret = (*itr)->SendChannelUserList(client);
break;
}
}
m_channels.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
ChatChannel* Chat::GetChannel(const char *channel_name) {
vector<ChatChannel *>::iterator itr;
ChatChannel* ret = 0;
m_channels.readlock(__FUNCTION__, __LINE__);
for (itr = channels.begin(); itr != channels.end(); itr++) {
if (strncasecmp(channel_name, (*itr)->GetName(), CHAT_CHANNEL_MAX_NAME) == 0) {
ret = (*itr);
break;
}
}
m_channels.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
#ifdef DISCORD
//this sends chat from EQ2EMu to Discord. Currently using webhooks. Makes things simpler code wise.
int Chat::PushDiscordMsg(const char* msg, const char* from) {
bool enablediscord = rule_manager.GetGlobalRule(R_Discord, DiscordEnabled)->GetBool();
if(enablediscord == false) {
LogWrite(INIT__INFO, 0,"Discord","Bot Disabled By Rule...");
return 0;
}
m_channels.readlock(__FUNCTION__, __LINE__);
const char* hook = rule_manager.GetGlobalRule(R_Discord, DiscordWebhookURL)->GetString();
std::string servername = net.GetWorldName();
char ourmsg[4096];
//form our message
sprintf(ourmsg,"[%s] [%s] Says: %s",from, servername.c_str(), msg);
/* send a message with this webhook */
dpp::cluster bot("");
dpp::webhook wh(hook);
bot.execute_webhook(wh, dpp::message(ourmsg));
m_channels.releasereadlock(__FUNCTION__, __LINE__);
return 1;
}
#endif

119
old/WorldServer/Chat/Chat.h Normal file
View File

@ -0,0 +1,119 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CHAT_CHAT_H_
#define CHAT_CHAT_H_
#include <vector>
#include "../../common/types.hpp"
#include "../../common/packet/eq_packet.hpp"
#include "../client.h"
#include "ChatChannel.h"
#ifdef DISCORD
#ifndef WIN32
#pragma once
#include <dpp/dpp.h>
#endif
#endif
using namespace std;
/*
CREATING A CHANNEL
-- OP_RemoteCmdMsg --
3/14/2012 20:17:06
192.168.1.198 -> 69.174.200.73
0000: 00 09 05 9A 2A 0E 00 0F 00 63 75 73 74 6F 6D 20 ....*....custom
0010 70 61 73 73 77 6F 72 64 password
TALKING IN A CHANNEL
[11:52.23] <@Xinux> -- OP_RemoteCmdMsg --
[11:52.23] <@Xinux> 3/14/2012 20:17:25
[11:52.23] <@Xinux> 192.168.1.198 -> 69.174.200.73
[11:52.23] <@Xinux> 0000: 00 09 06 2D 2A 11 00 21 00 63 75 73 74 6F 6D 20 ...-*..!.custom
[11:52.23] <@Xinux> 0010: 20 74 68 69 73 20 69 73 20 6D 79 20 63 75 73 74 this is my cust
[11:52.23] <@Xinux> 0020 6F 6D 20 63 68 61 6E 6E 65 6C om channel
[08:37.46] <@Xinux_Work> 00 09 05 8B 00 3A 53 00 00 00 FF 3C 02 00 00 FF .....:S....<....
[08:37.46] <@Xinux_Work> FF FF FF FF FF FF FF 06 00 4C 65 69 68 69 61 07 .........Leihia.
[08:37.46] <@Xinux_Work> 00 4B 6F 65 63 68 6F 68 00 02 00 00 00 00 01 00 .Koechoh........
[08:37.46] <@Xinux_Work> 00 00 22 00 18 00 62 65 74 74 65 72 20 74 68 61 .."...better tha
[08:37.46] <@Xinux_Work> 6E 20 61 20 72 65 64 20 6F 6E 65 20 3A 50 09 00 n a red one :P..
[08:37.46] <@Xinux_Work> 4C 65 76 65 6C 5F 31 2D 39 01 01 00 00 Level_1-9....
OTHERS LEAVING AND JOINING A CHANNEL
-- OP_ClientCmdMsg::OP_EqChatChannelUpdateCmd --
3/14/2012 20:17:06
69.174.200.73 -> 192.168.1.198
0000: 00 3A 18 00 00 00 FF 88 02 03 09 00 4C 65 76 65 .:..........Leve
0010 6C 5F 31 2D 39 07 00 53 68 61 77 6E 61 68 l_1-9..Shawnah
-- OP_ClientCmdMsg::OP_EqChatChannelUpdateCmd --
3/14/2012 20:17:06
69.174.200.73 -> 192.168.1.198
0000: 00 3A 16 00 00 00 FF 88 02 03 07 00 41 75 63 74 .:..........Auct
0010 69 6F 6E 07 00 53 68 61 77 6E 61 68 ion..Shawnah
OP_EqChatChannelUpdateCmd
unknown=0 unknown1=blank join
unknown=1 unknown1=blank leave
unknown=2 unknown2=player join/leave?
unknown=3 unknown2=player join/leave?
*/
class Chat{
public:
Chat();
virtual ~Chat();
void AddChannel(ChatChannel *channel);
unsigned int GetNumChannels();
EQ2Packet * GetWorldChannelList(Client *client);
bool ChannelExists(const char *channel_name);
bool HasPassword(const char *channel_name);
bool PasswordMatches(const char *channel_name, const char *password);
bool CreateChannel(const char *channel_name);
bool CreateChannel(const char *channel_name, const char *password);
bool IsInChannel(Client *client, const char *channel_name);
bool JoinChannel(Client *client, const char *channel_name);
bool LeaveChannel(Client *client, const char *channel_name);
bool LeaveAllChannels(Client *client);
bool TellChannel(Client *client, const char *channel_name, const char *message, const char* name = 0);
bool SendChannelUserList(Client *client, const char *channel_name);
//devn00b
int PushDiscordMsg(const char*, const char*);
ChatChannel* GetChannel(const char* channel_name);
private:
Mutex m_channels;
vector<ChatChannel *> channels;
};
#endif

View File

@ -0,0 +1,227 @@
#include <string.h>
#include "../../common/Log.h"
#include "../../common/config_reader.hpp"
#include "../../common/PacketStruct.h"
#include "../World.h"
#include "ChatChannel.h"
extern ConfigReader configReader;
extern ZoneList zone_list;
#define CHAT_CHANNEL_JOIN 0
#define CHAT_CHANNEL_LEAVE 1
#define CHAT_CHANNEL_OTHER_JOIN 2
#define CHAT_CHANNEL_OTHER_LEAVE 3
ChatChannel::ChatChannel() {
memset(name, 0, sizeof(name));
memset(password, 0, sizeof(password));
type = CHAT_CHANNEL_TYPE_NONE;
level_restriction = 0;
races = 0;
classes = 0;
}
ChatChannel::~ChatChannel() {
}
bool ChatChannel::IsInChannel(int32 character_id) {
vector<int32>::iterator itr;
for (itr = clients.begin(); itr != clients.end(); itr++) {
if (character_id == *itr)
return true;
}
return false;
}
bool ChatChannel::JoinChannel(Client *client) {
PacketStruct *packet_struct;
vector<int32>::iterator itr;
Client *to_client;
//send the player join packet to the joining client
if ((packet_struct = configReader.getStruct("WS_ChatChannelUpdate", client->GetVersion())) == NULL) {
LogWrite(CHAT__ERROR, 0, "Chat", "Could not find packet 'WS_ChatChannelUpdate' when client %s was trying to join channel %s", client->GetPlayer()->GetName(), name);
return false;
}
packet_struct->setDataByName("action", CHAT_CHANNEL_JOIN);
packet_struct->setDataByName("channel_name", name);
client->QueuePacket(packet_struct->serialize());
safe_delete(packet_struct);
clients.push_back(client->GetCharacterID());
//loop through everyone else in the channel and send the "other" player join packet
for (itr = clients.begin(); itr != clients.end(); itr++) {
if (client->GetCharacterID() == *itr)
continue;
if ((to_client = zone_list.GetClientByCharID(*itr)) == NULL)
continue;
if ((packet_struct = configReader.getStruct("WS_ChatChannelUpdate", to_client->GetVersion())) == NULL)
continue;
packet_struct->setDataByName("action", CHAT_CHANNEL_OTHER_JOIN);
packet_struct->setDataByName("channel_name", name);
packet_struct->setDataByName("player_name", client->GetPlayer()->GetName());
to_client->QueuePacket(packet_struct->serialize());
safe_delete(packet_struct);
}
return true;
}
bool ChatChannel::LeaveChannel(Client *client) {
vector<int32>::iterator itr;
PacketStruct *packet_struct;
Client *to_client;
bool ret = false;
for (itr = clients.begin(); itr != clients.end(); itr++) {
if (client->GetCharacterID() == *itr) {
clients.erase(itr);
ret = true;
break;
}
}
if (ret) {
//send the packet to the leaving client
if ((packet_struct = configReader.getStruct("WS_ChatChannelUpdate", client->GetVersion())) == NULL)
return false;
packet_struct->setDataByName("action", CHAT_CHANNEL_LEAVE);
packet_struct->setDataByName("channel_name", name);
client->QueuePacket(packet_struct->serialize());
safe_delete(packet_struct);
//send the leave packet to all other clients in the channel
for (itr = clients.begin(); itr != clients.end(); itr++) {
if ((to_client = zone_list.GetClientByCharID(*itr)) == NULL)
continue;
if (to_client == client) // don't need to send to self.
continue;
if ((packet_struct = configReader.getStruct("WS_ChatChannelUpdate", to_client->GetVersion())) == NULL)
continue;
packet_struct->setDataByName("action", CHAT_CHANNEL_OTHER_LEAVE);
packet_struct->setDataByName("channel_name", name);
packet_struct->setDataByName("player_name", client->GetPlayer()->GetName());
to_client->QueuePacket(packet_struct->serialize());
safe_delete(packet_struct);
}
}
return ret;
}
bool ChatChannel::TellChannel(Client *client, const char *message, const char* name2) {
vector<int32>::iterator itr;
PacketStruct *packet_struct;
Client *to_client;
for (itr = clients.begin(); itr != clients.end(); itr++) {
if ((to_client = zone_list.GetClientByCharID(*itr)) == NULL)
continue;
if ((packet_struct = configReader.getStruct("WS_HearChat", to_client->GetVersion())) == NULL)
continue;
packet_struct->setDataByName("unknown", 0);
packet_struct->setDataByName("from_spawn_id", 0xFFFFFFFF);
packet_struct->setDataByName("to_spawn_id", 0xFFFFFFFF);
if (client != NULL){
packet_struct->setDataByName("from", client->GetPlayer()->GetName());
} else {
char name3[128];
sprintf(name3,"[%s] from discord",name2);
packet_struct->setDataByName("from", name3);
}
packet_struct->setDataByName("to", to_client->GetPlayer()->GetName());
packet_struct->setDataByName("channel", to_client->GetMessageChannelColor(CHANNEL_CUSTOM_CHANNEL));
if(client != NULL){
packet_struct->setDataByName("language", client->GetPlayer()->GetCurrentLanguage());
}else{
packet_struct->setDataByName("language", 0);
}
packet_struct->setDataByName("message", message);
packet_struct->setDataByName("channel_name", name);
packet_struct->setDataByName("show_bubble", 1);
if(client != NULL){
if (client->GetPlayer()->GetCurrentLanguage() == 0 || to_client->GetPlayer()->HasLanguage(client->GetPlayer()->GetCurrentLanguage())) {
packet_struct->setDataByName("understood", 1);
}
} else {
packet_struct->setDataByName("understood", 1);
}
packet_struct->setDataByName("unknown4", 0);
to_client->QueuePacket(packet_struct->serialize());
safe_delete(packet_struct);
}
return true;
}
bool ChatChannel::TellChannelClient(Client* to_client, const char* message, const char* name2) {
PacketStruct *packet_struct;
if (string(name2).find('[') != string::npos)
return true;
packet_struct = configReader.getStruct("WS_HearChat", to_client->GetVersion());
if (packet_struct) {
packet_struct->setDataByName("unknown", 0);
packet_struct->setDataByName("from_spawn_id", 0xFFFFFFFF);
packet_struct->setDataByName("to_spawn_id", 0xFFFFFFFF);
packet_struct->setDataByName("from", name2);
packet_struct->setDataByName("to", to_client->GetPlayer()->GetName());
packet_struct->setDataByName("channel", to_client->GetMessageChannelColor(CHANNEL_CUSTOM_CHANNEL));
packet_struct->setDataByName("language", 0);
packet_struct->setDataByName("message", message);
packet_struct->setDataByName("channel_name", name);
packet_struct->setDataByName("show_bubble", 1);
packet_struct->setDataByName("understood", 1);
packet_struct->setDataByName("unknown4", 0);
to_client->QueuePacket(packet_struct->serialize());
}
safe_delete(packet_struct);
return true;
}
bool ChatChannel::SendChannelUserList(Client *client) {
vector<int32>::iterator itr;
PacketStruct *packet_struct;
Client *to_client;
int8 i = 0;
if ((packet_struct = configReader.getStruct("WS_WhoChannelQueryReply", client->GetVersion())) == NULL)
return false;
packet_struct->setDataByName("channel_name", name);
packet_struct->setDataByName("unknown", 0);
packet_struct->setArrayLengthByName("num_players", clients.size());
for (itr = clients.begin(); itr != clients.end(); itr++) {
if ((to_client = zone_list.GetClientByCharID(*itr)) != NULL)
packet_struct->setArrayDataByName("player_name", client->GetPlayer()->GetName(), i++);
else
packet_struct->setArrayDataByName("player_name", "<Unknown>", i++);
}
client->QueuePacket(packet_struct->serialize());
safe_delete(packet_struct);
return true;
}

View File

@ -0,0 +1,79 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CHAT_CHATCHANNEL_H_
#define CHAT_CHATCHANNEL_H_
#include "../../common/types.hpp"
#include "../client.h"
#include <vector>
using namespace std;
#define CHAT_CHANNEL_MAX_NAME 100
#define CHAT_CHANNEL_MAX_PASSWORD 100
enum ChatChannelType {
CHAT_CHANNEL_TYPE_NONE = 0,
CHAT_CHANNEL_TYPE_WORLD,
CHAT_CHANNEL_TYPE_CUSTOM
};
class ChatChannel {
public:
ChatChannel();
virtual ~ChatChannel();
void SetName(const char *name) {strncpy(this->name, name, CHAT_CHANNEL_MAX_NAME);}
void SetPassword(const char *password) {strncpy(this->password, password, CHAT_CHANNEL_MAX_PASSWORD);}
void SetType(ChatChannelType type) {this->type = type;}
void SetLevelRestriction(int16 level_restriction) {this->level_restriction = level_restriction;}
void SetRacesAllowed(int64 races) {this->races = races;}
void SetClassesAllowed(int64 classes) {this->classes = classes;}
const char * GetName() {return name;}
ChatChannelType GetType() {return type;}
unsigned int GetNumClients() {return clients.size();}
bool HasPassword() {return password[0] != '\0';}
bool PasswordMatches(const char *password) {return strncmp(this->password, password, CHAT_CHANNEL_MAX_PASSWORD) == 0;}
bool CanJoinChannelByLevel(int16 level) {return level >= level_restriction;}
bool CanJoinChannelByRace(int8 race_id) {return races == 0 || (1 << race_id) & races;}
bool CanJoinChannelByClass(int8 class_id) {return classes == 0 || (1 << class_id) & classes;}
bool IsInChannel(int32 character_id);
bool JoinChannel(Client *client);
bool LeaveChannel(Client *client);
bool TellChannel(Client *client, const char *message, const char* name2 = 0);
bool TellChannelClient(Client* to_client, const char* message, const char* name2 = 0);
bool SendChannelUserList(Client *client);
private:
char name[CHAT_CHANNEL_MAX_NAME + 1];
char password[CHAT_CHANNEL_MAX_PASSWORD + 1];
ChatChannelType type;
vector<int32> clients;
int16 level_restriction;
int64 races;
int64 classes;
};
#endif

View File

@ -0,0 +1,46 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../../common/Log.h"
#include "Chat.h"
#include "../Worlddatabase.hpp"
extern Chat chat;
void WorldDatabase::LoadChannels() {
DatabaseResult result;
ChatChannel *channel;
if (database_new.Select(&result, "SELECT `name`,`password`,`level_restriction`,`classes`,`races` FROM `channels`")) {
while (result.Next()) {
channel = new ChatChannel();
channel->SetName(result.GetString(0));
if (!result.IsNull(1))
channel->SetPassword(result.GetString(1));
channel->SetLevelRestriction(result.GetInt16(2));
channel->SetClassesAllowed(result.GetInt64(3));
channel->SetRacesAllowed(result.GetInt64(4));
channel->SetType(CHAT_CHANNEL_TYPE_WORLD);
chat.AddChannel(channel);
}
}
}

View File

@ -0,0 +1,475 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ClientPacketFunctions.h"
#include "WorldDatabase.hpp"
#include "../common/config_reader.hpp"
#include "Variables.h"
#include "World.h"
#include "classes.h"
#include "../common/log.hpp"
#include "Traits/Traits.h"
extern Classes classes;
extern Commands commands;
extern WorldDatabase database;
extern ConfigReader configReader;
extern MasterSpellList master_spell_list;
extern MasterTraitList master_trait_list;
extern Variables variables;
extern World world;
void ClientPacketFunctions::SendFinishedEntitiesList ( Client* client ){
EQ2Packet* finishedEntitiesApp = new EQ2Packet(OP_DoneSendingInitialEntitiesMsg, 0, 0);
client->QueuePacket(finishedEntitiesApp);
}
void ClientPacketFunctions::SendSkillSlotMappings(Client* client){
EQ2Packet* app = client->GetPlayer()->GetSpellSlotMappingPacket(client->GetVersion());
if(app)
client->QueuePacket(app);
}
void ClientPacketFunctions::SendLoginDenied ( Client* client ){
PacketStruct* packet = configReader.getStruct("LS_LoginResponse", 1);
if(packet){
packet->setDataByName("reply_code", 1);
packet->setDataByName("unknown03", 0xFFFFFFFF);
packet->setDataByName("unknown04", 0xFFFFFFFF);
EQ2Packet* app = packet->serialize();
client->QueuePacket(app);
safe_delete(packet);
}
}
void ClientPacketFunctions::SendLoginAccepted ( Client* client ){
LogWrite(PACKET__DEBUG, 0, "Packet", "Sending Login Accepted packet (LS_LoginResponse, %i)", client->GetVersion());
PacketStruct* response_packet = configReader.getStruct("LS_LoginResponse", client->GetVersion());
if(response_packet){
response_packet->setDataByName("unknown02", 1);
response_packet->setDataByName("unknown05", -959971393);
response_packet->setDataByName("unknown08", 2);
response_packet->setDataByName("unknown09", 585);
response_packet->setDataByName("unknown10", 1597830);
response_packet->setDataByName("accountid", 3); //client->GetAccountID());
EQ2Packet* outapp = response_packet->serialize();
client->QueuePacket(outapp);
safe_delete(response_packet);
}
}
void ClientPacketFunctions::SendCommandList ( Client* client ){
EQ2Packet* app = commands.GetRemoteCommands()->serialize(client->GetVersion());
client->QueuePacket(app);
}
void ClientPacketFunctions::SendGameWorldTime ( Client* client ){
PacketStruct* packet = world.GetWorldTime(client->GetVersion());
if(packet){
client->QueuePacket(packet->serialize());
safe_delete(packet);
}
//opcode 501 was the selection display opcode
}
void ClientPacketFunctions::SendCharacterData ( Client* client ){
client->GetPlayer()->SetCharacterID(client->GetCharacterID());
if(!client->IsReloadingZone()) {
EQ2Packet* outapp = client->GetPlayer()->serialize(client->GetPlayer(), client->GetVersion());
//DumpPacket(outapp);
client->QueuePacket(outapp);
}
}
void ClientPacketFunctions::SendCharacterSheet ( Client* client ){
EQ2Packet* app = client->GetPlayer()->GetPlayerInfo()->serialize(client->GetVersion());
client->QueuePacket(app);
if (client->GetVersion() >= 1188) {
EQ2Packet* app2 = client->GetPlayer()->GetPlayerInfo()->serializePet(client->GetVersion());
if (app2)
client->QueuePacket(app2);
}
}
void ClientPacketFunctions::SendSkillBook ( Client* client ){
EQ2Packet* app = client->GetPlayer()->skill_list.GetSkillPacket(client->GetVersion());
if(app)
client->QueuePacket(app);
}
// Jabantiz: Attempt to get the char trait list working
void ClientPacketFunctions::SendTraitList(Client* client) {
if (client->GetVersion() >= 562) {
EQ2Packet* traitApp = master_trait_list.GetTraitListPacket(client);
//DumpPacket(traitApp);
if (traitApp) {
client->QueuePacket(traitApp);
}
}
}
void ClientPacketFunctions::SendAbilities ( Client* client ){
LogWrite(MISC__TODO, 1, "TODO", " Add SendAbilities functionality\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__);
// this is the featherfall ability data
// later this would loop through and send all abilities
/*uchar abilityData[] ={0x11,0x00,0x00,0x00,0xff,0x15,0x02,0x00,0x0b,0x00,0x46,0x65,0x61,0x74
,0x68,0x65,0x72,0x66,0x61,0x6c,0x6c};
EQ2Packet* abilityApp = new EQ2Packet(OP_ClientCmdMsg, abilityData, sizeof(abilityData));
client->QueuePacket(abilityApp);*/
}
void ClientPacketFunctions::SendCommandNamePacket ( Client* client ){
LogWrite(MISC__TODO, 1, "TODO", " fix, this is actually quest/collection information\n\t(%s, function: %s, line #: %i)", __FILE__, __FUNCTION__, __LINE__);
/*
PacketStruct* command_packet = configReader.getStruct("WS_CommandName", client->GetVersion());
if(command_packet){
command_packet->setDataByName("unknown03", 0x221bfb47);
char* charName = { "BogusName" };
command_packet->setMediumStringByName("character_name",charName);
EQ2Packet* outapp = command_packet->serialize();
client->QueuePacket(outapp);
safe_delete(command_packet);
}
*/
}
void ClientPacketFunctions::SendQuickBarInit ( Client* client ){
int32 count = database.LoadPlayerSkillbar(client);
if(count == 0) {
LogWrite(PACKET__DEBUG, 0, "Packet", "No character quickbar found!");
database.UpdateStartingSkillbar(client->GetCharacterID(), client->GetPlayer()->GetAdventureClass(), client->GetPlayer()->GetRace());
database.LoadPlayerSkillbar(client);
}
EQ2Packet* quickbarApp = client->GetPlayer()->GetQuickbarPacket(client->GetVersion());
if(quickbarApp)
client->QueuePacket(quickbarApp);
}
void ClientPacketFunctions::SendCharacterMacros(Client* client) {
LogWrite(PACKET__DEBUG, 0, "Packet", "Sending Character Macro packet (WS_MacroInit, %i)", client->GetVersion());
map<int8, vector<MacroData*> >* macros = database.LoadCharacterMacros(client->GetCharacterID());
if (macros) {
PacketStruct* macro_packet = configReader.getStruct("WS_MacroInit", client->GetVersion());
if (macro_packet) {
map<int8, vector<MacroData*> >::iterator itr;
macro_packet->setArrayLengthByName("macro_count", macros->size());
int8 x = 0;
for (itr = macros->begin(); itr != macros->end(); itr++, x++) {
macro_packet->setArrayDataByName("number", itr->first, x);
if (itr->second.size() > 0) {
LogWrite(PACKET__DEBUG, 5, "Packet", "Loading Macro %i, name: %s", itr->first, itr->second[0]->name.c_str());
macro_packet->setArrayDataByName("name", itr->second[0]->name.c_str(), x);
}
if (client->GetVersion() > 373) {
char tmp_details_count[25] = { 0 };
sprintf(tmp_details_count, "macro_details_count_%i", x);
macro_packet->setArrayLengthByName(tmp_details_count, itr->second.size());
for (int8 i = 0; i < itr->second.size(); i++) {
char tmp_command[15] = { 0 };
sprintf(tmp_command, "command%i", x);
LogWrite(PACKET__DEBUG, 5, "Packet", "\tLoading Command %i: %s", itr->first, x, itr->second[i]->text.c_str());
macro_packet->setArrayDataByName(tmp_command, itr->second[i]->text.c_str(), i);
if ( i > 0 ) // itr->second[0] used below, we will delete it later
safe_delete(itr->second[i]); // delete MacroData*
}
macro_packet->setArrayDataByName("unknown2", 2, x);
macro_packet->setArrayDataByName("unknown3", 0xFFFFFFFF, x);
}
else {
if (itr->second.size() > 0)
macro_packet->setArrayDataByName("command", itr->second[0]->text.c_str(), x);
}
macro_packet->setArrayDataByName("icon", itr->second[0]->icon, x);
client->GetPlayer()->macro_icons[itr->first] = itr->second[0]->icon;
// remove itr->second[0] now that we are done with it
safe_delete(itr->second[0]); // delete MacroData*
}
EQ2Packet* packet = macro_packet->serialize();
client->QueuePacket(packet);
safe_delete(macro_packet);
}
safe_delete(macros);
}
}
void ClientPacketFunctions::SendMOTD ( Client* client ){
const char* motd = 0;
// fetch MOTD from `variables` table
Variable* var = variables.FindVariable("motd");
if( var == NULL || strlen (var->GetValue()) == 0) {
LogWrite(WORLD__WARNING, 0, "World", "No MOTD set. Sending generic message...");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Message of the Day: Welcome to EQ2Emulator! Customize this message in the `variables`.`motd` data!");
}
else {
motd = var->GetValue();
LogWrite(WORLD__DEBUG, 0, "World", "Send MOTD...");
client->SimpleMessage(CHANNEL_COLOR_YELLOW, motd);
}
}
void ClientPacketFunctions::SendUpdateSpellBook ( Client* client ){
if(client->IsReadyForSpawns()){
EQ2Packet* app = client->GetPlayer()->GetSpellBookUpdatePacket(client->GetVersion());
if(app)
client->QueuePacket(app);
}
client->GetPlayer()->UnlockAllSpells(true);
}
void ClientPacketFunctions::SendServerControlFlagsClassic(Client* client, int32 param, int32 value) {
PacketStruct* packet = configReader.getStruct("WS_ServerControlFlags", client->GetVersion());
if(packet) {
packet->setDataByName("parameter", param);
packet->setDataByName("value", value);
client->QueuePacket(packet->serialize());
}
safe_delete(packet);
}
void ClientPacketFunctions::SendServerControlFlags(Client* client, int8 param, int8 param_val, int8 value) {
PacketStruct* packet = configReader.getStruct("WS_ServerControlFlags", client->GetVersion());
if(packet) {
if (param == 1)
packet->setDataByName("parameter1", param_val);
else if (param == 2)
packet->setDataByName("parameter2", param_val);
else if (param == 3)
packet->setDataByName("parameter3", param_val);
else if (param == 4)
packet->setDataByName("parameter4", param_val);
else if (param == 5)
packet->setDataByName("parameter5", param_val);
else {
safe_delete(packet);
return;
}
packet->setDataByName("value", value);
client->QueuePacket(packet->serialize());
/*
Some other values for this packet
first param:
01 flymode
02 collisons off
04 unknown
08 heading movement only
16 forward/reverse movement only
32 low gravity
64 sit
second
2 crouch
third:
04 float when trying to jump, no movement
08 jump high, no movement
128 walk underwater
fourth:
01 moon jump underwater
04 fear
16 moon jumps
32 safe fall (float to ground)
64 cant move
fifth:
01 die
08 hover (fae)
32 flymode2?
*/
}
safe_delete(packet);
}
void ClientPacketFunctions::SendInstanceList(Client* client) {
if (client->GetPlayer()->GetCharacterInstances()->GetInstanceCount() > 0) {
PacketStruct* packet = configReader.getStruct("WS_InstanceCreated", client->GetVersion());
if (packet) {
vector<InstanceData> persist = client->GetPlayer()->GetCharacterInstances()->GetPersistentInstances();
vector<InstanceData> lockout = client->GetPlayer()->GetCharacterInstances()->GetLockoutInstances();
packet->setArrayLengthByName("num_instances", lockout.size());
for (int32 i = 0; i < lockout.size(); i++) {
InstanceData data = lockout.at(i);
packet->setArrayDataByName("unknown1", data.db_id, i); // unique id per player
packet->setArrayDataByName("instance_zone_name", data.zone_name.c_str(), i);
packet->setArrayDataByName("unknown2", 0x0B, i); // Always set to 0x0B on live packets
packet->setArrayDataByName("success_last", data.last_success_timestamp, i);
packet->setArrayDataByName("last_failure", data.last_failure_timestamp, i);
packet->setArrayDataByName("failure", data.failure_lockout_time, i);
packet->setArrayDataByName("success", data.success_lockout_time, i);
}
packet->setArrayLengthByName("num_persistent", persist.size());
for (int32 i = 0; i < persist.size(); i++) {
InstanceData data = persist.at(i);
packet->setArrayDataByName("unknown1a", data.db_id, i); // unique id per player
packet->setArrayDataByName("persistent_zone_name", data.zone_name.c_str(), i);
packet->setArrayDataByName("unknown2a", 0x0B, i); // set to 0x0B in all live packets
packet->setArrayDataByName("persist_success_timestamp", data.last_success_timestamp, i);
packet->setArrayDataByName("persist_failure_timestamp", data.last_failure_timestamp, i);
// Check min duration (last success + failure)
//if (Timer::GetUnixTimeStamp() < data.last_success_timestamp + data.failure_lockout_time*/)
//packet->setArrayDataByName("unknown3b", 1, i);
packet->setArrayDataByName("unknown3b", 1, i);
packet->setArrayDataByName("minimum_duration", data.failure_lockout_time, i);
packet->setArrayDataByName("maximum_duration", data.success_lockout_time, i);
packet->setArrayDataByName("unknown4a", 1800, i); // All live logs have 0x0708
}
client->QueuePacket(packet->serialize());
}
safe_delete(packet);
}
}
void ClientPacketFunctions::SendMaintainedExamineUpdate(Client* client, int8 slot_pos, int32 update_value, int8 update_type){
if (!client)
return;
PacketStruct* packet = configReader.getStruct("WS_UpdateMaintainedExamine", client->GetVersion());
if (packet){
packet->setSubstructDataByName("info_header", "show_name", 1);
packet->setSubstructDataByName("info_header", "packettype", 19710);
packet->setSubstructDataByName("info_header", "packetsubtype", 5);
packet->setDataByName("time_stamp", Timer::GetCurrentTime2());
packet->setDataByName("slot_pos", slot_pos);
packet->setDataByName("update_value", update_value > 0 ? update_value : 0xFFFFFFFF);
packet->setDataByName("update_type", update_type);
client->QueuePacket(packet->serialize());
safe_delete(packet);
}
}
void ClientPacketFunctions::SendZoneChange(Client* client, char* zone_ip, int16 zone_port, int32 key) {
if (!client)
return;
PacketStruct* packet = configReader.getStruct("WS_ZoneChangeMsg", client->GetVersion());
if (packet) {
packet->setDataByName("account_id", client->GetAccountID());
packet->setDataByName("key", key);
packet->setDataByName("ip_address", zone_ip);
packet->setDataByName("port", zone_port);
client->QueuePacket(packet->serialize());
}
safe_delete(packet);
}
void ClientPacketFunctions::SendStateCommand(Client* client, int32 spawn_id, int32 state) {
if (!client || !spawn_id) {
return;
}
PacketStruct* packet = configReader.getStruct("WS_StateCmd", client->GetVersion());
if (packet) {
packet->setDataByName("spawn_id", spawn_id);
packet->setDataByName("state", state);
client->QueuePacket(packet->serialize());
}
safe_delete(packet);
}
void ClientPacketFunctions::SendFlyMode(Client* client, int8 flymode, bool updateCharProperty)
{
if (updateCharProperty)
database.insertCharacterProperty(client, CHAR_PROPERTY_FLYMODE, (char*)std::to_string(flymode).c_str());
if(client->GetVersion() <= 561) {
if(flymode) {
// old flymode
SendServerControlFlagsClassic(client, flymode, 1);
if(flymode == 1) {
// disable noclip
SendServerControlFlagsClassic(client, 2, 0);
}
}
else {
// disable flymode and noclip
SendServerControlFlagsClassic(client, 2, 0);
SendServerControlFlagsClassic(client, 1, 0);
}
}
else {
if(flymode == 2) {
// new flymode + noclip
SendServerControlFlags(client, 5, 32, 1);
SendServerControlFlags(client, 1, 2, 1);
}
else if(flymode == 1) {
// new flymode
SendServerControlFlags(client, 5, 32, 1);
SendServerControlFlags(client, 1, 2, 0);
}
else {
// disable flymode and noclip
SendServerControlFlags(client, 5, 32, 0);
SendServerControlFlags(client, 1, 2, 0);
}
}
client->Message(CHANNEL_STATUS, "Flymode %s, No Clip %s", flymode > 0 ? "on" : "off", flymode > 1 ? "on" : "off");
/*
CLASSIC/DOF ONLY HAS THE FIRST SET OF FLAGS
Some other values for this packet
first param:
01 flymode
02 collisons off
04 unknown
08 forward movement
16 heading movement
32 low gravity
64 sit
EVERYTHING BELOW NOT SUPPORTED BY CLASSIC/DOF
second
2 crouch
third:
04 float when trying to jump, no movement
08 jump high, no movement
fourth:
04 autorun (fear?)
16 moon jumps
32 safe fall (float to ground)
64 cant move
fifth:
01 die
08 hover (fae)
32 flymode2?
*/
}

View File

@ -0,0 +1,92 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "client.h"
struct HouseZone;
struct PlayerHouse;
struct HeroicOP;
class ClientPacketFunctions
{
public:
static void SendFinishedEntitiesList ( Client* client );
static void SendLoginDenied ( Client* client );
static void SendLoginAccepted ( Client* client );
static void SendCommandList ( Client* client );
static void SendGameWorldTime ( Client* client );
static void SendCharacterData ( Client* client );
static void SendCharacterSheet ( Client* client );
static void SendSkillBook ( Client* client );
static void SendTraitList ( Client* client );
static void SendAbilities ( Client* client );
static void SendCommandNamePacket ( Client* client );
static void SendQuickBarInit ( Client* client );
static void SendMOTD ( Client* client );
static void SendCharacterMacros(Client* client);
static void SendUpdateSpellBook ( Client* client );
static void SendSkillSlotMappings(Client* client);
static void SendRestartZoneMsg(Client* client);
static void SendServerControlFlags(Client* client, int8 param, int8 param_val, int8 value);
static void SendServerControlFlagsClassic(Client* client, int32 param, int32 value);
static void SendInstanceList(Client* client);
static void SendZoneChange(Client* client, char* zone_ip, int16 zone_port, int32 key);
static void SendStateCommand(Client* client, int32 spawn_id, int32 state);
static void SendFlyMode(Client* client, int8 flymode, bool updateCharProperty=true);
/* Tradeskills (/Tradeskills/TradeskillsPackets.cpp) */
static void SendCreateFromRecipe(Client* client, int32 recipeID);
static void SendItemCreationUI(Client* client, Recipe* recipe);
static void StopCrafting(Client* client);
static void CounterReaction(Client* client, bool countered);
static void SendAchievementList(Client* client);
/* Housing (/Housing/HousingPackets.cpp) */
static void SendHousePurchase(Client* client, HouseZone* hz, int32 spawnID);
static void SendHousingList(Client* client);
static void SendBaseHouseWindow(Client* client, HouseZone* hz, PlayerHouse* ph, int32 spawnID);
static void SendHouseVisitWindow(Client* client, vector<PlayerHouse*> houses);
static void SendLocalizedTextMessage(Client* client);
/* Heroic OP's (/HeroicOp/HeroicOpPackets.cpp) */
static void SendHeroicOPUpdate(Client* client, HeroicOP* ho);
//UI updates for trigger count and damage remaining on maintained spells
static void SendMaintainedExamineUpdate(Client* client, int8 slot_pos, int32 update_value, int8 update_type);
};

View File

@ -0,0 +1,317 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Collections.h"
#include "../../common/Log.h"
#include <assert.h>
extern MasterCollectionList master_collection_list;
Collection::Collection() {
id = 0;
memset(name, 0, sizeof(name));
memset(category, 0, sizeof(category));
level = 0;
reward_coin = 0;
reward_xp = 0;
completed = false;
save_needed = false;
}
Collection::Collection(Collection *in) {
vector<struct CollectionItem *> *collection_items_in;
vector<struct CollectionRewardItem *> *reward_items_in;
vector<struct CollectionItem *>::iterator itr;
vector<struct CollectionRewardItem *>::iterator itr2;
struct CollectionItem *collection_item;
struct CollectionRewardItem *reward_item;
assert(in);
id = in->GetID();
strncpy(name, in->GetName(), sizeof(name));
strncpy(category, in->GetCategory(), sizeof(category));
level = in->GetLevel();
reward_coin = in->GetRewardCoin();
reward_xp = in->GetRewardXP();
completed = in->GetCompleted();
save_needed = in->GetSaveNeeded();
collection_items_in = in->GetCollectionItems();
for (itr = collection_items_in->begin(); itr != collection_items_in->end(); itr++) {
collection_item = new struct CollectionItem;
collection_item->item = (*itr)->item;
collection_item->index = (*itr)->index;
collection_item->found = (*itr)->found;
collection_items.push_back(collection_item);
}
reward_items_in = in->GetRewardItems();
for (itr2 = reward_items_in->begin(); itr2 != reward_items_in->end(); itr2++) {
reward_item = new struct CollectionRewardItem;
reward_item->item = (*itr2)->item;
reward_item->quantity = (*itr2)->quantity;
reward_items.push_back(reward_item);
}
reward_items_in = in->GetSelectableRewardItems();
for (itr2 = reward_items_in->begin(); itr2 != reward_items_in->end(); itr2++) {
reward_item = new struct CollectionRewardItem;
reward_item->item = (*itr2)->item;
reward_item->quantity = (*itr2)->quantity;
selectable_reward_items.push_back(reward_item);
}
}
Collection::~Collection() {
vector<struct CollectionItem *>::iterator itr;
vector<struct CollectionRewardItem *>::iterator itr2;
for (itr = collection_items.begin(); itr != collection_items.end(); itr++)
safe_delete(*itr);
for (itr2 = reward_items.begin(); itr2 != reward_items.end(); itr2++)
safe_delete(*itr2);
for (itr2 = selectable_reward_items.begin(); itr2 != selectable_reward_items.end(); itr2++)
safe_delete(*itr2);
}
void Collection::AddCollectionItem(struct CollectionItem *collection_item) {
assert(collection_item);
collection_items.push_back(collection_item);
}
void Collection::AddRewardItem(struct CollectionRewardItem *reward_item) {
assert(reward_item);
reward_items.push_back(reward_item);
}
void Collection::AddSelectableRewardItem(struct CollectionRewardItem *reward_item) {
assert(reward_item);
selectable_reward_items.push_back(reward_item);
}
bool Collection::NeedsItem(Item *item) {
vector<struct CollectionItem *>::iterator itr;
struct CollectionItem *collection_item;
assert(item);
if (completed)
return false;
for (itr = collection_items.begin(); itr != collection_items.end(); itr++) {
collection_item = *itr;
if (collection_item->item == item->details.item_id) {
if (collection_item->found)
return false;
else
return true;
}
}
/* item is not required by this collection at all */
return false;
}
struct CollectionItem * Collection::GetCollectionItemByItemID(int32 item_id) {
vector<struct CollectionItem *>::iterator itr;
struct CollectionItem *collection_item;
for (itr = collection_items.begin(); itr != collection_items.end(); itr++) {
collection_item = *itr;
if (collection_item->item == item_id)
return collection_item;
}
return 0;
}
bool Collection::GetIsReadyToTurnIn() {
vector<struct CollectionItem *>::iterator itr;
if (completed)
return false;
for (itr = collection_items.begin(); itr != collection_items.end(); itr++) {
if (!(*itr)->found)
return false;
}
return true;
}
MasterCollectionList::MasterCollectionList() {
mutex_collections.SetName("MasterCollectionList::collections");
}
MasterCollectionList::~MasterCollectionList() {
ClearCollections();
}
bool MasterCollectionList::AddCollection(Collection *collection) {
bool ret = false;
assert(collection);
mutex_collections.writelock(__FUNCTION__, __LINE__);
if (collections.count(collection->GetID()) == 0) {
collections[collection->GetID()] = collection;
ret = true;
}
mutex_collections.releasewritelock(__FUNCTION__, __LINE__);
return ret;
}
Collection * MasterCollectionList::GetCollection(int32 collection_id) {
Collection *collection = 0;
mutex_collections.readlock(__FUNCTION__, __LINE__);
if (collections.count(collection_id) > 0)
collection = collections[collection_id];
mutex_collections.releasereadlock(__FUNCTION__, __LINE__);
return collection;
}
void MasterCollectionList::ClearCollections() {
map<int32, Collection *>::iterator itr;
mutex_collections.writelock(__FUNCTION__, __LINE__);
for (itr = collections.begin(); itr != collections.end(); itr++)
safe_delete(itr->second);
collections.clear();
mutex_collections.releasewritelock(__FUNCTION__, __LINE__);
}
int32 MasterCollectionList::Size() {
int32 size;
mutex_collections.readlock(__FUNCTION__, __LINE__);
size = collections.size();
mutex_collections.releasereadlock(__FUNCTION__, __LINE__);
return size;
}
bool MasterCollectionList::NeedsItem(Item *item) {
map<int32, Collection *>::iterator itr;
bool ret = false;
assert(item);
mutex_collections.readlock(__FUNCTION__, __LINE__);
for (itr = collections.begin(); itr != collections.end(); itr++) {
if (itr->second->NeedsItem(item)) {
ret = true;
break;
}
}
mutex_collections.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
PlayerCollectionList::PlayerCollectionList() {
}
PlayerCollectionList::~PlayerCollectionList() {
ClearCollections();
}
bool PlayerCollectionList::AddCollection(Collection *collection) {
assert(collection);
if (collections.count(collection->GetID()) == 0) {
collections[collection->GetID()] = collection;
return true;
}
return false;
}
Collection * PlayerCollectionList::GetCollection(int32 collection_id) {
if (collections.count(collection_id) > 0)
return collections[collection_id];
return 0;
}
void PlayerCollectionList::ClearCollections() {
map<int32, Collection *>::iterator itr;
for (itr = collections.begin(); itr != collections.end(); itr++)
safe_delete(itr->second);
collections.clear();
}
int32 PlayerCollectionList::Size() {
return collections.size();
}
bool PlayerCollectionList::NeedsItem(Item *item) {
map<int32, Collection *> *master_collections;
map<int32, Collection *>::iterator itr;
Collection *collection;
Mutex *master_mutex;
bool ret = false;
assert(item);
for (itr = collections.begin(); itr != collections.end(); itr++) {
if (itr->second->NeedsItem(item)) {
ret = true;
break;
}
}
/* if the player doesnt have a collection that needs the item, check the master collection list to see if there's a collection
* in there that needs the item that the player does not have yet */
if (!ret) {
master_mutex = master_collection_list.GetMutex();
master_collections = master_collection_list.GetCollections();
master_mutex->readlock(__FUNCTION__, __LINE__);
for (itr = master_collections->begin(); itr != master_collections->end(); itr++) {
collection = itr->second;
if (collection->NeedsItem(item) && !GetCollection(collection->GetID())) {
ret = true;
break;
}
}
master_mutex->releasereadlock(__FUNCTION__, __LINE__);
}
return ret;
}
bool PlayerCollectionList::HasCollectionsToHandIn() {
map<int32, Collection *>::iterator itr;
for (itr = collections.begin(); itr != collections.end(); itr++) {
if (itr->second->GetIsReadyToTurnIn())
return true;
}
return false;
}

View File

@ -0,0 +1,106 @@
#ifndef COLLECTIONS_H_
#define COLLECTIONS_H_
#include "../../common/types.hpp"
#include "../Items/Items.h"
#include <map>
#include <vector>
using namespace std;
struct CollectionItem {
int32 item;
int8 index;
int8 found;
};
struct CollectionRewardItem {
Item *item;
int8 quantity;
};
class Collection {
public:
Collection();
Collection(Collection *in);
virtual ~Collection();
void SetID(int32 id) {this->id = id;}
void SetName(const char *name) {strncpy(this->name, name, sizeof(this->name));}
void SetCategory(const char *category) {strncpy(this->category, category, sizeof(this->category));}
void SetLevel(int8 level) {this->level = level;}
void SetCompleted(bool completed) {this->completed = completed;}
void SetSaveNeeded(bool save_needed) {this->save_needed = save_needed;}
void AddCollectionItem(struct CollectionItem *collection_item);
void AddRewardItem(struct CollectionRewardItem *reward_item);
void AddSelectableRewardItem(struct CollectionRewardItem *reward_item);
void SetRewardCoin(int64 reward_coin) {this->reward_coin = reward_coin;}
void SetRewardXP(int64 reward_xp) {this->reward_xp = reward_xp;}
bool NeedsItem(Item *item);
struct CollectionItem * GetCollectionItemByItemID(int32 item_id);
int32 GetID() {return id;}
const char * GetName() {return name;}
const char * GetCategory() {return category;}
int8 GetLevel() {return level;}
bool GetIsReadyToTurnIn();
bool GetCompleted() {return completed;}
bool GetSaveNeeded() {return save_needed;}
vector<struct CollectionItem *> * GetCollectionItems() {return &collection_items;}
vector<struct CollectionRewardItem *> * GetRewardItems() {return &reward_items;}
vector<struct CollectionRewardItem *> * GetSelectableRewardItems() {return &selectable_reward_items;}
int64 GetRewardCoin() {return reward_coin;}
int64 GetRewardXP() {return reward_xp;}
private:
int32 id;
char name[512];
char category[512];
int8 level;
int64 reward_coin;
int64 reward_xp;
bool completed;
bool save_needed;
vector<struct CollectionItem *> collection_items;
vector<struct CollectionRewardItem *> reward_items;
vector<struct CollectionRewardItem *> selectable_reward_items;
};
class MasterCollectionList {
public:
MasterCollectionList();
virtual ~MasterCollectionList();
bool AddCollection(Collection *collection);
Collection * GetCollection(int32 collection_id);
void ClearCollections();
int32 Size();
bool NeedsItem(Item *item);
Mutex * GetMutex() {return &mutex_collections;}
map<int32, Collection *> * GetCollections() {return &collections;}
private:
Mutex mutex_collections;
map<int32, Collection *> collections;
};
class PlayerCollectionList {
public:
PlayerCollectionList();
virtual ~PlayerCollectionList();
bool AddCollection(Collection *collection);
Collection * GetCollection(int32 collection_id);
void ClearCollections();
int32 Size();
bool NeedsItem(Item *item);
bool HasCollectionsToHandIn();
map<int32, Collection *> * GetCollections() {return &collections;}
private:
map<int32, Collection *> collections;
};
#endif

View File

@ -0,0 +1,295 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef WIN32
#include <WinSock2.h>
#include <windows.h>
#endif
#include <mysql.h>
#include <assert.h>
#include "../../common/Log.h"
#include "../Worlddatabase.hpp"
#include "Collections.h"
extern MasterCollectionList master_collection_list;
void WorldDatabase::LoadCollections()
{
Collection *collection;
Query query;
MYSQL_ROW row;
MYSQL_RES *res;
int32 cItems_total = 0;
int32 cItems_rewards = 0;
res = query.RunQuery2(Q_SELECT, "SELECT `id`,`collection_name`,`collection_category`,`level`\n"
"FROM `collections`");
if (res)
{
while ((row = mysql_fetch_row(res)))
{
collection = new Collection();
collection->SetID(atoul(row[0]));
collection->SetName(row[1]);
collection->SetCategory(row[2]);
collection->SetLevel(atoi(row[3]));
LogWrite(COLLECTION__DEBUG, 5, "Collect", "\tLoading Collection: '%s' (%u)", collection->GetName(),collection->GetID());
if (!master_collection_list.AddCollection(collection))
{
LogWrite(COLLECTION__ERROR, 0, "Collect", "Error adding collection '%s' - duplicate ID: %u", collection->GetName(),collection->GetID());
safe_delete(collection);
continue;
}
cItems_total += LoadCollectionItems(collection);
cItems_rewards += LoadCollectionRewards(collection);
}
}
LogWrite(COLLECTION__DEBUG, 0, "Collect", "\tLoaded %u collections", master_collection_list.Size());
LogWrite(COLLECTION__DEBUG, 0, "Collect", "\tLoaded %u collection items", cItems_total);
LogWrite(COLLECTION__DEBUG, 0, "Collect", "\tLoaded %u collection rewards", cItems_rewards);
}
int32 WorldDatabase::LoadCollectionItems(Collection *collection)
{
struct CollectionItem *collection_item;
Item *item;
Query query;
MYSQL_ROW row;
MYSQL_RES *res;
int32 total = 0;
assert(collection);
res = query.RunQuery2(Q_SELECT, "SELECT `item_id`,`item_index`\n"
"FROM `collection_details`\n"
"WHERE `collection_id`=%u\n"
"ORDER BY `item_index` ASC",
collection->GetID());
if (res)
{
while ((row = mysql_fetch_row(res)))
{
if ((item = master_item_list.GetItem(atoul(row[0]))))
{
collection_item = new struct CollectionItem;
collection_item->item = atoul(row[0]);
collection_item->index = atoi(row[1]);
collection_item->found = 0;
LogWrite(COLLECTION__DEBUG, 5, "Collect", "\tLoading Collection Item: (%u)", atoul(row[0])); //LogWrite(COLLECTION__DEBUG, 5, "Collect", "\tLoading Collection Item: '%s' (%u)", master_item_list.GetItem(collection_item->item)->name.c_str(), atoul(row[0]));
collection->AddCollectionItem(collection_item);
total++;
}
}
}
if(query.GetErrorNumber())
LogWrite(COLLECTION__ERROR, 0, "Collect", "Error Loading Collection Items, Query: %s, Error: %s", query.GetQuery(), query.GetError());
return total;
}
int32 WorldDatabase::LoadCollectionRewards(Collection *collection)
{
struct CollectionRewardItem *reward_item;
Query query;
MYSQL_ROW row;
MYSQL_RES *res;
int32 total = 0;
assert(collection);
res = query.RunQuery2(Q_SELECT, "SELECT `reward_type`,`reward_value`,`reward_quantity`\n"
"FROM `collection_rewards`\n"
"WHERE `collection_id`=%u",
collection->GetID());
if (res)
{
while ((row = mysql_fetch_row(res)))
{
LogWrite(COLLECTION__DEBUG, 5, "Collect", "\tLoading Collection Reward: Type: %s, Val: %s, Qty: %u", row[0], row[1], atoi(row[2]));
if (!strcasecmp(row[0], "Item"))
{
reward_item = new struct CollectionRewardItem;
reward_item->item = master_item_list.GetItem(atoul(row[1]));
reward_item->quantity = atoi(row[2]);
collection->AddRewardItem(reward_item);
total++;
}
else if (!strcasecmp(row[0], "Selectable"))
{
reward_item = new struct CollectionRewardItem;
reward_item->item = master_item_list.GetItem(atoul(row[1]));
reward_item->quantity = atoi(row[2]);
collection->AddSelectableRewardItem(reward_item);
total++;
}
else if (!strcasecmp(row[0], "Coin"))
{
collection->SetRewardCoin(atoi64(row[1]));
total++;
}
else if (!strcasecmp(row[0], "XP"))
{
collection->SetRewardXP(atoi64(row[1]));
total++;
}
else
LogWrite(COLLECTION__ERROR, 0, "Collect", "Error adding collection reward to collection '%s'. Unknown reward type '%s'", collection->GetName(), row[0]);
}
}
if(query.GetErrorNumber())
LogWrite(COLLECTION__ERROR, 0, "Collect", "Error Loading Collection Rewards, Query: %s, Error: %s", query.GetQuery(), query.GetError());
return total;
}
void WorldDatabase::LoadPlayerCollections(Player *player)
{
Collection *collection;
Query query;
MYSQL_ROW row;
MYSQL_RES *res;
assert(player);
res = query.RunQuery2(Q_SELECT, "SELECT `collection_id`,`completed` FROM `character_collections` WHERE `char_id`=%u", player->GetCharacterID());
if (res)
{
while ((row = mysql_fetch_row(res)))
{
collection = new Collection(master_collection_list.GetCollection(atoul(row[0])));
collection->SetCompleted(atoi(row[1]));
if (!player->GetCollectionList()->AddCollection(collection))
{
LogWrite(COLLECTION__ERROR, 0, "Collect", "Error adding collection %u to player '%s' - duplicate ID\n", collection->GetID(), player->GetName());
safe_delete(collection);
continue;
}
LoadPlayerCollectionItems(player, collection);
}
}
if(query.GetErrorNumber())
LogWrite(COLLECTION__ERROR, 0, "Collect", "Error Loading Character Collections, Query: %s, Error: %s", query.GetQuery(), query.GetError());
}
void WorldDatabase::LoadPlayerCollectionItems(Player *player, Collection *collection)
{
struct CollectionItem *collection_item;
Query query;
MYSQL_ROW row;
MYSQL_RES *res;
assert(player);
assert(collection);
res = query.RunQuery2(Q_SELECT, "SELECT `collection_item_id`\n"
"FROM `character_collection_items`\n"
"WHERE `char_id`=%u\n"
"AND `collection_id`=%u",
player->GetCharacterID(), collection->GetID());
if (res)
{
while ((row = mysql_fetch_row(res)))
{
if ((collection_item = collection->GetCollectionItemByItemID(atoul(row[0]))))
collection_item->found = true;
else
LogWrite(COLLECTION__ERROR, 0, "Collect", "Error Loading character collection items. Item ID %u does not exist in collection %s", atoul(row[0]), collection->GetName());
}
}
if(query.GetErrorNumber())
LogWrite(COLLECTION__ERROR, 0, "Collect", "Error Loading Character Collection Items, Query: %s, Error: %s", query.GetQuery(), query.GetError());
}
void WorldDatabase::SavePlayerCollections(Client *client)
{
map<int32, Collection *> *collections;
map<int32, Collection *>::iterator itr;
Collection *collection;
assert(client);
collections = client->GetPlayer()->GetCollectionList()->GetCollections();
for (itr = collections->begin(); itr != collections->end(); itr++)
{
collection = itr->second;
if (collection->GetSaveNeeded())
{
SavePlayerCollection(client, collection);
SavePlayerCollectionItems(client, collection);
collection->SetSaveNeeded(false);
}
}
}
void WorldDatabase::SavePlayerCollection(Client *client, Collection *collection)
{
Query query;
assert(client);
assert(collection);
query.AddQueryAsync(client->GetCharacterID(), this, Q_UPDATE, "INSERT INTO `character_collections` (`char_id`,`collection_id`,`completed`)\n"
"VALUES (%u,%u,0)\n"
"ON DUPLICATE KEY UPDATE `completed`=%i",
client->GetPlayer()->GetCharacterID(), collection->GetID(),
collection->GetCompleted() ? 1 : 0);
}
void WorldDatabase::SavePlayerCollectionItems(Client *client, Collection *collection)
{
vector<struct CollectionItem *> *collection_items;
vector<struct CollectionItem *>::iterator itr;
struct CollectionItem *collection_item;
assert(client);
assert(collection);
collection_items = collection->GetCollectionItems();
for (itr = collection_items->begin(); itr != collection_items->end(); itr++)
{
collection_item = *itr;
if (collection_item->found > 0)
SavePlayerCollectionItem(client, collection, collection_item->item);
}
}
void WorldDatabase::SavePlayerCollectionItem(Client *client, Collection *collection, int32 item_id)
{
Query query;
assert(client);
assert(collection);
//assert(item);
query.AddQueryAsync(client->GetCharacterID(), this, Q_INSERT, "INSERT IGNORE INTO `character_collection_items` (`char_id`,`collection_id`,`collection_item_id`)\n"
"VALUES (%u,%u,%u)",
client->GetPlayer()->GetCharacterID(), collection->GetID(), item_id);
}

2068
old/WorldServer/Combat.cpp Normal file

File diff suppressed because it is too large Load Diff

33
old/WorldServer/Combat.h Normal file
View File

@ -0,0 +1,33 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __EQ2_COMBAT_H__
#define __EQ2_COMBAT_H__
#include "Player.h"
#include "../common/timer.hpp"
#include "NPC_AI.h"
#include "MutexList.h"
class ZoneServer;
class SpellProcess;
class LuaSpell;
#endif

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,316 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef WIN32
#include <WinSock2.h>
#include <windows.h>
#endif
#include <mysql.h>
#include <assert.h>
#include "../../common/Log.h"
#include "../Worlddatabase.hpp"
#include "Commands.h"
#include "ConsoleCommands.h"
map<int32, string>* WorldDatabase::GetSpawnTemplateListByName(const char* name)
{
LogWrite(COMMAND__DEBUG, 0, "Command", "Player listing spawn templates by template name ('%s')...", name);
map<int32, string>* ret = 0;
string template_name = "";
Query query;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, name FROM spawn_templates WHERE name RLIKE '%s' LIMIT 0,10", getSafeEscapeString(name).c_str());
if(result && mysql_num_rows(result) > 0)
{
ret = new map<int32, string>;
MYSQL_ROW row;
while(result && (row = mysql_fetch_row(result)))
{
template_name = string(row[1]);
(*ret)[atoul(row[0])] = template_name;
LogWrite(COMMAND__DEBUG, 5, "Command", "\t%u. '%s'", atoul(row[0]), template_name.c_str());
}
}
return ret;
}
map<int32, string>* WorldDatabase::GetSpawnTemplateListByID(int32 location_id)
{
LogWrite(COMMAND__DEBUG, 0, "Command", "Player listing spawn templates by LocaionID: %u...", location_id);
map<int32, string>* ret = 0;
string template_name = "";
Query query;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, name FROM spawn_templates WHERE spawn_location_id = %u", location_id);
if(result && mysql_num_rows(result) > 0)
{
ret = new map<int32, string>;
MYSQL_ROW row;
while(result && (row = mysql_fetch_row(result)))
{
template_name = string(row[1]);
(*ret)[atoul(row[0])] = template_name;
LogWrite(COMMAND__DEBUG, 5, "Command", "\t%u. '%s'", atoul(row[0]), template_name.c_str());
}
}
return ret;
}
int32 WorldDatabase::SaveSpawnTemplate(int32 placement_id, const char* template_name)
{
Query query;
string str_name = getSafeEscapeString(template_name).c_str();
LogWrite(COMMAND__DEBUG, 0, "Command", "Player saving spawn template '%s' for placement_id %u...", str_name.c_str(), placement_id);
query.RunQuery2(Q_INSERT, "INSERT INTO spawn_templates (name, spawn_location_id) VALUES ('%s', %u)", str_name.c_str(), placement_id);
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in SaveSpawnTemplate query '%s': %s", query.GetQuery(), query.GetError());
return 0;
}
int32 ret = query.GetLastInsertedID();
LogWrite(COMMAND__DEBUG, 0, "Command", "Success! Returning TemplateID: %u...", ret);
return ret;
}
bool WorldDatabase::RemoveSpawnTemplate(int32 template_id)
{
Query query;
LogWrite(COMMAND__DEBUG, 0, "Command", "Player removing spawn template ID %u...", template_id);
query.RunQuery2(Q_DELETE, "DELETE FROM spawn_templates WHERE id = %u", template_id);
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in RemoveSpawnTemplate query '%s': %s", query.GetQuery(), query.GetError());
return false;
}
if (query.GetAffectedRows() > 0 )
{
LogWrite(COMMAND__DEBUG, 0, "Command", "Success! Removed spawn template ID %u...", template_id);
return true;
}
return false;
}
int32 WorldDatabase::CreateSpawnFromTemplateByID(Client* client, int32 template_id)
{
Query query, query2, query3, query4, query5, query6;
MYSQL_ROW row;
int32 spawn_location_id = 0;
float new_x = client->GetPlayer()->GetX();
float new_y = client->GetPlayer()->GetY();
float new_z = client->GetPlayer()->GetZ();
float new_heading = client->GetPlayer()->GetHeading();
LogWrite(COMMAND__DEBUG, 0, "Command", "Creating spawn point from templateID %u...", template_id);
LogWrite(COMMAND__DEBUG, 5, "Command", "\tCoords: %.2f %.2f %.2f...", new_x, new_y, new_z);
// find the spawn_location_id in the template we plan to duplicate
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT spawn_location_id FROM spawn_templates WHERE id = %u", template_id);
if (result && (row = mysql_fetch_row(result))) {
if (row[0])
spawn_location_id = atoi(row[0]);
}
if( spawn_location_id > 0 )
{
LogWrite(COMMAND__DEBUG, 5, "Command", "\tUsing LocationID: %u...", spawn_location_id);
// insert a new spawn_location_name record
string name = "TemplateGenerated";
query2.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_name (name) VALUES ('%s')", name.c_str());
if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query2.GetQuery(), query2.GetError());
return 0;
}
int32 new_location_id = query2.GetLastInsertedID();
LogWrite(COMMAND__DEBUG, 5, "Command", "Created new Spawn Location: '%s' (%u)", name.c_str(), new_location_id);
// get all spawn_location_entries that match the templates spawn_location_id value and insert as new
LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_entry(s) for location_id %u", spawn_location_id);
MYSQL_RES* result2 = query3.RunQuery2(Q_SELECT, "SELECT spawn_id, spawnpercentage FROM spawn_location_entry WHERE spawn_location_id = %u", spawn_location_id);
if(result2 && mysql_num_rows(result2) > 0){
MYSQL_ROW row2;
while(result2 && (row2 = mysql_fetch_row(result2)) && row2[0])
{
query4.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) VALUES (%u, %u, %i)",
atoul(row2[0]), new_location_id, atoi(row2[1]));
if(query4.GetErrorNumber() && query4.GetError() && query4.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query4.GetQuery(), query4.GetError());
return 0;
}
LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Entry for spawn_id %u, location_id %u, percentage %i", atoul(row2[0]), new_location_id, atoi(row2[1]));
}
}
// get all spawn_location_placements that match the templates spawn_location_id value and insert as new
// Note: /spawn templates within current zone_id only, because of spawn_id issues (cannot template an Antonic spawn in Commonlands)
LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_placement(s) for location_id %u", spawn_location_id);
MYSQL_RES* result3 = query5.RunQuery2(Q_SELECT, "SELECT zone_id, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id FROM spawn_location_placement WHERE spawn_location_id = %u", spawn_location_id);
if(result3 && mysql_num_rows(result3) > 0){
MYSQL_ROW row3;
while(result3 && (row3 = mysql_fetch_row(result3)) && row3[0])
{
query6.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_placement (zone_id, spawn_location_id, x, y, z, heading, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id) VALUES (%i, %u, %2f, %2f, %2f, %2f, %2f, %2f, %2f, %i, %i, %i, %u)",
atoi(row3[0]), new_location_id, new_x, new_y, new_z, new_heading, atof(row3[1]), atof(row3[2]), atof(row3[3]), atoi(row3[4]), atoi(row3[5]), atoi(row3[6]), atoul(row3[7]));
if(query6.GetErrorNumber() && query6.GetError() && query6.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query6.GetQuery(), query6.GetError());
return 0;
}
LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Placement at new coords for location_id %u", new_location_id);
}
}
LogWrite(COMMAND__DEBUG, 0, "Command", "Success! New spawn(s) from TemplateID %u created from location %u", template_id, spawn_location_id);
return new_location_id;
}
return 0;
}
int32 WorldDatabase::CreateSpawnFromTemplateByName(Client* client, const char* template_name)
{
Query query, query1, query2, query3, query4, query5, query6;
MYSQL_ROW row;
int32 template_id = 0;
int32 spawn_location_id = 0;
float new_x = client->GetPlayer()->GetX();
float new_y = client->GetPlayer()->GetY();
float new_z = client->GetPlayer()->GetZ();
float new_heading = client->GetPlayer()->GetHeading();
LogWrite(COMMAND__DEBUG, 0, "Command", "Creating spawn point from template '%s'...", template_name);
LogWrite(COMMAND__DEBUG, 5, "Command", "\tCoords: %.2f %.2f %.2f...", new_x, new_y, new_z);
// find the spawn_location_id in the template we plan to duplicate
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT id, spawn_location_id FROM spawn_templates WHERE name = '%s'", template_name);
if (result && (row = mysql_fetch_row(result))) {
if (row[0])
{
template_id = atoul(row[0]);
spawn_location_id = atoi(row[1]);
}
}
if( spawn_location_id > 0 )
{
LogWrite(COMMAND__DEBUG, 5, "Command", "\tUsing LocationID: %u...", spawn_location_id);
// insert a new spawn_location_name record
string name = "TemplateGenerated";
query2.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_name (name) VALUES ('%s')", name.c_str());
if(query2.GetErrorNumber() && query2.GetError() && query2.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query2.GetQuery(), query2.GetError());
return 0;
}
int32 new_location_id = query2.GetLastInsertedID();
LogWrite(COMMAND__DEBUG, 5, "Command", "Created new Spawn Location: '%s' (%u)", name.c_str(), new_location_id);
// get all spawn_location_entries that match the templates spawn_location_id value and insert as new
LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_entry(s) for location_id %u", spawn_location_id);
MYSQL_RES* result2 = query3.RunQuery2(Q_SELECT, "SELECT spawn_id, spawnpercentage FROM spawn_location_entry WHERE spawn_location_id = %u", spawn_location_id);
if(result2 && mysql_num_rows(result2) > 0){
MYSQL_ROW row2;
while(result2 && (row2 = mysql_fetch_row(result2)) && row2[0])
{
query4.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_entry (spawn_id, spawn_location_id, spawnpercentage) VALUES (%u, %u, %i)",
atoul(row2[0]), new_location_id, atoi(row2[1]));
if(query4.GetErrorNumber() && query4.GetError() && query4.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query4.GetQuery(), query4.GetError());
return 0;
}
LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Entry for spawn_id %u, location_id %u, percentage %i", atoul(row2[0]), new_location_id, atoi(row2[1]));
}
}
// get all spawn_location_placements that match the templates spawn_location_id value and insert as new
// Note: /spawn templates within current zone_id only, because of spawn_id issues (cannot template an Antonic spawn in Commonlands)
LogWrite(COMMAND__DEBUG, 5, "Command", "Finding existing spawn_location_placement(s) for location_id %u", spawn_location_id);
MYSQL_RES* result3 = query5.RunQuery2(Q_SELECT, "SELECT zone_id, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id FROM spawn_location_placement WHERE spawn_location_id = %u", spawn_location_id);
if(result3 && mysql_num_rows(result3) > 0){
MYSQL_ROW row3;
while(result3 && (row3 = mysql_fetch_row(result3)) && row3[0])
{
query6.RunQuery2(Q_INSERT, "INSERT INTO spawn_location_placement (zone_id, spawn_location_id, x, y, z, heading, x_offset, y_offset, z_offset, respawn, expire_timer, expire_offset, grid_id) VALUES (%i, %u, %2f, %2f, %2f, %2f, %2f, %2f, %2f, %i, %i, %i, %u)",
atoi(row3[0]), new_location_id, new_x, new_y, new_z, new_heading, atof(row3[1]), atof(row3[2]), atof(row3[3]), atoi(row3[4]), atoi(row3[5]), atoi(row3[6]), atoul(row3[7]));
if(query6.GetErrorNumber() && query6.GetError() && query6.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(COMMAND__ERROR, 0, "Command", "Error in CreateSpawnFromTemplateByID query '%s': %s", query6.GetQuery(), query6.GetError());
return 0;
}
LogWrite(COMMAND__DEBUG, 5, "Command", "Insert Placement at new coords for location_id %u", new_location_id);
}
}
LogWrite(COMMAND__DEBUG, 0, "Command", "Success! New spawn(s) from TemplateID %u created from location %u", template_id, spawn_location_id);
return new_location_id;
}
return 0;
}
bool WorldDatabase::SaveZoneSafeCoords(int32 zone_id, float x, float y, float z, float heading)
{
Query query;
LogWrite(COMMAND__DEBUG, 0, "Command", "Setting safe coords for zone %u (X: %.2f, Y: %.2f, Z: %.2f, H: %.2f)", zone_id, x, y, z, heading);
query.RunQuery2(Q_UPDATE, "UPDATE zones SET safe_x = %f, safe_y = %f, safe_z = %f, safe_heading = %f WHERE id = %u", x, y, z, heading, zone_id);
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(DATABASE__ERROR, 0, "DBCore", "Error in SaveZoneSafeCoords query '%s': %s", query.GetQuery(), query.GetError());
return false;
}
if (query.GetAffectedRows() > 0 )
{
LogWrite(COMMAND__DEBUG, 0, "Command", "Success! Set new safe coordinates in zone ID %u...", zone_id);
return true;
}
LogWrite(COMMAND__ERROR, 0, "Command", "FAILED! Set new safe coordinates in zone ID %u...", zone_id);
return false;
}
bool WorldDatabase::SaveSignZoneToCoords(int32 spawn_id, float x, float y, float z, float heading)
{
Query query;
LogWrite(COMMAND__DEBUG, 0, "Command", "Setting Zone-To coords for Spawn ID %u (X: %.2f, Y: %.2f, Z: %.2f, H: %.2f)", spawn_id, x, y, z, heading);
query.RunQuery2(Q_UPDATE, "UPDATE spawn_signs SET zone_x = %f, zone_y = %f, zone_z = %f, zone_heading = %f WHERE spawn_id = %u", x, y, z, heading, spawn_id);
if(query.GetErrorNumber() && query.GetError() && query.GetErrorNumber() < 0xFFFFFFFF){
LogWrite(DATABASE__ERROR, 0, "DBCore", "Error in SaveSignZoneToCoords query '%s': %s", query.GetQuery(), query.GetError());
return false;
}
if (query.GetAffectedRows() > 0 )
{
LogWrite(COMMAND__DEBUG, 0, "Command", "Success! Set new Zone-To coordinates in zone ID %u...", spawn_id);
return true;
}
LogWrite(COMMAND__ERROR, 0, "Command", "FAILED! Set new Zone-To coordinates in zone ID %u...", spawn_id);
return false;
}

View File

@ -0,0 +1,549 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include <iostream>
using namespace std;
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "../../common/debug.hpp"
#include "../../common/Log.h"
#include "../../common/seperator.h"
#include "ConsoleCommands.h"
#include "../World.h"
#include "../Rules/Rules.h"
#include "../Worlddatabase.hpp"
extern volatile bool RunLoops;
bool ContinueLoops = false;
extern Variables variables;
extern ZoneList zone_list;
extern RuleManager rule_manager;
extern WorldDatabase database;
void ProcessConsoleInput(const char * cmdInput)
{
static ConsoleCommand Commands[] = {
// account controls
{ &ConsoleBanCommand, "ban", "[player] {duration} {reason}", "Ban player with {optional} duration and reason." },
{ &ConsoleUnbanCommand, "unban", "[player]", "Unban a player." },
{ &ConsoleKickCommand, "kick", "[player] {reason}", "Kick player with {optional} reason." },
// chat controls
{ &ConsoleAnnounceCommand, "announce", "[message]", "Sends Announcement message to all channels/clients." },
{ &ConsoleBroadcastCommand, "broadcast","[message]", "Sends Broadcast message to all channels/clients." },
{ &ConsoleChannelCommand, "channel", "[channel] [message]", "Sends Channel message to channel." },
{ &ConsoleTellCommand, "tell", "[player] [message]", "Sends Private message to player." },
// world system controls
{ &ConsoleGuildCommand, "guild", "[params]", "" },
{ &ConsolePlayerCommand, "player", "[params]", "" },
{ &ConsoleSetAdminPlayer, "makeadmin", "[charname] [status=0]", "" },
{ &ConsoleZoneCommand, "zone", "[command][value]", "command = help to get help" },
{ &ConsoleWorldCommand, "world", "[params]", "" },
{ &ConsoleGetMOTDCommand, "getmotd", "", "Display current MOTD" },
{ &ConsoleSetMOTDCommand, "setmotd", "[new motd]", "Sets a new MOTD" },
/// misc controls
{ &ConsoleWhoCommand, "who", "{zone id | player}", "Shows who is online globally, or in a given zone." },
{ &ConsoleReloadCommand, "reload", "[all | [type]]", "Reload main systems." },
{ &ConsoleRulesCommand, "rules", "{zone} {id}", "Show Global Ruleset (or Zone ruleset {optional})" },
{ &ConsoleShutdownCommand, "shutdown", "[delay]", "Gracefully shutdown world in [delay] sesconds." },
{ &ConsoleCancelShutdownCommand,"cancel", "", "Cancel shutdown command." },
{ &ConsoleExitCommand, "exit", "", "Brutally kills the world without mercy." },
{ &ConsoleExitCommand, "quit", "", "Brutally kills the world without mercy." },
{ &ConsoleTestCommand, "test", "", "Dev testing command." },
{ NULL, NULL, NULL, NULL },
};
Seperator *sep = new Seperator(cmdInput, ' ', 20, 100, true);
bool found = false;
uint32 i;
if (!sep)
return;
if (!strcasecmp(sep->arg[0], "help") || sep->arg[0][0] == 'h' || sep->arg[0][0] == 'H' || sep->arg[0][0] == '?') {
found = true;
printf("======================================================================================================\n");
printf("| %10s | %30s | %52s |\n", "Name", "Params", "Description");
printf("======================================================================================================\n");
for (i = 0; Commands[i].Name != NULL; i++) {
printf("| %10s | %30s | %52s |\n", Commands[i].Name, Commands[i].ParameterFormat, Commands[i].Description);
}
printf("======================================================================================================\n");
printf("-[ Help formatted for 120 chars wide screen ]-\n");
}
else {
for (i = 0; Commands[i].Name != NULL; ++i) {
if (!strcasecmp(Commands[i].Name, sep->arg[0])) {
found = true;
if (!Commands[i].CommandPointer(sep))
printf("\nError, incorrect syntax for '%s'.\n Correct syntax is: '%s'.\n\n", Commands[i].Name, Commands[i].ParameterFormat );
}
}
}
if (!found)
printf("Invalid Command '%s'! Type '?' or 'help' to get a command list.\n\n", sep->arg[0]);
fflush(stdout);
delete sep;
}
/************************************************* COMMANDS *************************************************/
bool ConsoleBanCommand(Seperator *sep)
{
if( strlen(sep->arg[1]) == 0 )
return false;
return true;
}
bool ConsoleUnbanCommand(Seperator *sep)
{
if( strlen(sep->arg[1]) == 0 )
return false;
return true;
}
bool ConsoleKickCommand(Seperator *sep)
{
if( strlen(sep->arg[1]) == 0 )
return false;
return true;
}
bool ConsoleAnnounceCommand(Seperator *sep)
{
if( strlen(sep->arg[1]) == 0 )
return false;
return true;
}
bool ConsoleBroadcastCommand(Seperator *sep)
{
if( strlen(sep->arg[1]) == 0 )
return false;
char message[4096];
snprintf(message, sizeof(message), "%s %s", "BROADCAST:", sep->argplus[1]);
zone_list.HandleGlobalBroadcast(message);
return true;
}
bool ConsoleChannelCommand(Seperator *sep)
{
if( strlen(sep->arg[1]) == 0 )
return false;
return true;
}
bool ConsoleTellCommand(Seperator *sep)
{
if( strlen(sep->arg[1]) == 0 )
return false;
return true;
}
bool ConsoleWhoCommand(Seperator *sep)
{
// zone_list.ProcessWhoQuery(who, client);
if (!strcasecmp(sep->arg[1], "zone")) {
printf("Who's Online in Zone");
if (sep->IsNumber(2)) {
printf("ID %s:\n", sep->arg[2]);
printf("===============================================================================\n");
printf("| %10s | %62s |\n", "CharID", "Name");
printf("===============================================================================\n");
}
else {
printf(" '%s':\n", sep->arg[2]);
printf("===============================================================================\n");
printf("| %10s | %62s |\n", "CharID", "Name");
printf("===============================================================================\n");
}
}
else {
printf("Who's Online (Global):\n");
printf("===============================================================================\n");
printf("| %10s | %20s | %39s |\n", "CharID", "Name", "Zone");
printf("===============================================================================\n");
}
printf("Not Implemented... yet :)\n");
printf("===============================================================================\n");
return true;
}
bool ConsoleGuildCommand(Seperator *sep)
{
if( strlen(sep->arg[1]) == 0 )
return false;
return true;
}
bool ConsolePlayerCommand(Seperator *sep)
{
if( strlen(sep->arg[1]) == 0 )
return false;
return true;
}
bool ConsoleSetAdminPlayer(Seperator *sep)
{
if(!sep->arg[1] || strlen(sep->arg[1]) == 0)
return false;
sint16 status = 0;
if(sep->IsNumber(2))
status = atoi(sep->arg[2]);
Client* client = zone_list.GetClientByCharName(sep->arg[1]);
if(!client) {
printf("Client not found by char name, must be logged in\n");
return true;
}
if(!client->GetPlayer()) {
printf("Player is not available for client class, try again\n");
return true;
}
client->SetAdminStatus(status);
if(status)
client->SimpleMessage(CHANNEL_COLOR_YELLOW, "Admin status updated.");
database.UpdateAdminStatus(client->GetPlayer()->GetName(), status);
printf("Admin status for %s is updated to %i\n", client->GetPlayer()->GetName(), status);
return true;
}
bool ConsoleWorldCommand(Seperator *sep)
{
if( strlen(sep->arg[1]) == 0 )
return false;
return true;
}
bool ConsoleZoneCommand(Seperator *sep)
{
if( strlen(sep->arg[1]) == 0 ) // has to be at least 1 arg (command)
return false;
ZoneServer* zone = 0;
if( strlen(sep->arg[2]) == 0 )
{
// process commands without values
if (!strcasecmp(sep->arg[1], "active") )
{
// not correct, but somehow need to access the Private zlist from World.h ???
list<ZoneServer*> zlist;
list<ZoneServer*>::iterator zone_iter;
ZoneServer* tmp = 0;
int zonesListed = 0;
printf("> List Active Zones...\n");
printf("======================================================================================================\n");
printf("| %7s | %30s | %10s | %42s |\n", "ID", "Name", "Instance", "Description");
printf("======================================================================================================\n");
for(zone_iter=zlist.begin(); zone_iter!=zlist.end(); zone_iter++){
tmp = *zone_iter;
zonesListed++;
printf("| %7d | %30s | %10d | %42s |\n", tmp->GetZoneID(), tmp->GetZoneName(), tmp->GetInstanceID(),tmp->GetZoneDescription());
}
return true;
}
else if (!strcasecmp(sep->arg[1], "help") || sep->arg[1][0] == '?')
{
printf("======================================================================================================\n");
printf("| %10s | %30s | %52s |\n", "Command", "Value", "Description");
printf("======================================================================================================\n");
printf("| %10s | %30s | %52s |\n", "active", "n/a", "List currently active zones");
printf("| %10s | %30s | %52s |\n", "list", "[name]", "Lookup zone by name");
printf("| %10s | %30s | %52s |\n", "status", "[zone_id | name | ALL]", "List zone stats");
printf("| %10s | %30s | %52s |\n", "lock", "[zone_id | name]", "Locks a zone");
printf("| %10s | %30s | %52s |\n", "unlock", "[zone_id | name]", "Unlocks a zone");
printf("| %10s | %30s | %52s |\n", "shutdown", "[zone_id | name | ALL]", "Gracefully shuts down a zone");
printf("| %10s | %30s | %52s |\n", "kill", "[zone_id | name | ALL]", "Terminates a zone");
printf("======================================================================================================\n");
return true;
}
else
return false;
}
else
{
if( !strcasecmp(sep->arg[1], "list") )
{
const char* name = 0;
name = sep->argplus[2];
map<int32, string>* zone_names = database.GetZoneList(name);
if(!zone_names)
{
printf("> No zones found.\n");
}
else
{
printf("> List zones matching '%s'...\n", sep->arg[2]);
printf("====================================================\n");
printf("| %3s | %42s |\n", "ID", "Name");
printf("====================================================\n");
map<int32, string>::iterator itr;
for(itr = zone_names->begin(); itr != zone_names->end(); itr++)
printf("| %3u | %42s |\n", itr->first, itr->second.c_str());
safe_delete(zone_names);
printf("====================================================\n");
}
return true;
}
if( !strcasecmp(sep->arg[1], "lock") )
{
if( sep->IsNumber(2) )
printf("> Locking zone ID %i...\n", atoul(sep->arg[2]));
else
printf("> Locking zone '%s'...\n", sep->arg[2]);
return true;
}
if( !strcasecmp(sep->arg[1], "unlock") )
{
if( strlen(sep->arg[2]) > 0 && sep->IsNumber(2) )
printf("> Unlocking zone ID %i...\n", atoi(sep->arg[2]));
else
printf("> Unlocking zone '%s'...\n", sep->arg[2]);
return true;
}
if( !strcasecmp(sep->arg[1], "status") )
{
if( sep->IsNumber(2) )
{
ZoneChangeDetails zone_details;
if( zone_list.GetZone(&zone_details, atoi(sep->arg[2]), "", false, false, false) )
{
printf("> Zone status for zone ID %i...\n", atoi(sep->arg[2]));
printf("============================================================================================\n");
printf("| %30s | %10s | %42s |\n", "Zone", "Param", "Value");
printf("============================================================================================\n");
printf("| %30s | %10s | %42s |\n", zone_details.zoneName, "locked", zone_details.lockState ? "true" : "false");
}
else
{
printf("> Zone ID %i not running, so not locked.\n", atoi(sep->arg[2]));
}
}
else if( !strcasecmp(sep->arg[2], "ALL") )
{
printf("> Zone status for ALL active zones...\n");
}
else
{
printf("> Zone status for zone '%s'...\n", sep->arg[2]);
}
return true;
}
if( !strcasecmp(sep->arg[1], "shutdown") )
{
if( sep->IsNumber(2) )
printf("> Shutdown zone ID %i...\n", atoi(sep->arg[2]));
else if( !strcasecmp(sep->arg[2], "ALL") )
printf("> Shutdown ALL active zones...\n");
else
printf("> Shutdown zone '%s'...\n", sep->arg[2]);
return true;
}
if( !strcasecmp(sep->arg[1], "kill") )
{
if( sep->IsNumber(2) )
printf("> Kill zone ID %i...\n", atoi(sep->arg[2]));
else if( !strcasecmp(sep->arg[2], "ALL") )
printf("> Kill ALL active zones...\n");
else
printf("> Kill zone '%s'...\n", sep->arg[2]);
return true;
}
}
return false;
}
bool ConsoleGetMOTDCommand(Seperator *sep)
{
const char* motd = 0;
Variable* var = variables.FindVariable("motd");
if( var == NULL || strlen (var->GetValue()) == 0){
printf("No MOTD.");
}
else{
motd = var->GetValue();
printf("%s\n", motd);
}
return true;
}
bool ConsoleSetMOTDCommand(Seperator *sep)
{
if( strlen(sep->arg[1]) == 0 )
return false;
return true;
}
bool ConsoleReloadCommand(Seperator *sep)
{
#ifdef _WIN32
HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
SetConsoleTextAttribute(console, FOREGROUND_YELLOW_BOLD);
#else
printf("\033[1;33m");
#endif
printf("Usage: ");
#ifdef _WIN32
SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD);
#else
printf("\033[1;37m");
#endif
printf("reload [type]\n");
#ifdef _WIN32
SetConsoleTextAttribute(console, 8);
#else
printf("\033[0m");
#endif
printf("Valid [type] paramters are:\n");
printf("===============================================================================\n");
printf("| %21s | %51s |\n", "all", "Reloads all systems (why not just restart?)");
printf("| %21s | %51s |\n", "structs", "Reloads structs (XMLs)");
printf("| %21s | %51s |\n", "items", "Reload Items data");
printf("| %21s | %51s |\n", "luasystem", "Reload LUA System Scripts");
printf("| %21s | %51s |\n", "spawnscripts", "Reload SpawnScripts");
printf("| %21s | %51s |\n", "quests", "Reload Quest Data and Scripts");
printf("| %21s | %51s |\n", "spawns", "Reload ALL Spawns from DB");
printf("| %21s | %51s |\n", "groundspawn_items", "Reload Groundspawn Items lists");
printf("| %21s | %51s |\n", "zonescripts", "Reload Zone Scripts");
printf("| %21s | %51s |\n", "entity_commands", "Reload Entity Commands");
printf("| %21s | %51s |\n", "factions", "Reload Factions");
printf("| %21s | %51s |\n", "mail", "Reload in-game Mail data");
printf("| %21s | %51s |\n", "guilds", "Reload Guilds");
printf("| %21s | %51s |\n", "locations", "Reload Locations data");
printf("===============================================================================\n");
if( strlen(sep->arg[1]) > 0 ) {
// handle reloads here
if (!strcasecmp(sep->arg[1], "spawns"))
zone_list.ReloadSpawns();
}
return true;
}
bool ConsoleShutdownCommand(Seperator *sep)
{
if ( IsNumber(sep->arg[1]) ) {
int8 shutdown_delay = atoi(sep->arg[1]);
printf("Shutdown World in %i second(s)...\n", shutdown_delay);
// shutting down gracefully, warn players.
char message[4096];
snprintf(message, sizeof(message), "BROADCAST: Server is shutting down in %s second(s)", sep->arg[1]);
zone_list.HandleGlobalBroadcast(message);
Sleep(shutdown_delay * 1000);
}
else {
printf("Shutdown World immediately... you probably won't even see this message, huh!\n");
}
if( !ContinueLoops )
RunLoops = false;
return true;
}
bool ConsoleCancelShutdownCommand(Seperator *sep)
{
printf("Cancel World Shutdown...\n");
ContinueLoops = true;
return true;
}
bool ConsoleExitCommand(Seperator *sep)
{
// I wanted this to be a little more Terminate-y... killkillkill
printf("Terminate World immediately...\n");
RunLoops = false;
return true;
}
bool ConsoleRulesCommand(Seperator *sep)
{
/*if( strlen(sep->arg[1]) == 0 )
return false;*/
printf("Current Active Ruleset");
if (!strcasecmp(sep->arg[1], "zone"))
{
if (sep->IsNumber(2)) {
printf(" in Zone ID: %s\n", sep->arg[2]);
}
else
return false;
}
else
{
printf(" (global):\n");
}
printf("===============================================================================\n");
printf("| %20s | %20s | %29s |\n", "Category", "Type", "Value");
printf("===============================================================================\n");
return true;
}
bool ConsoleTestCommand(Seperator *sep)
{
// devs put whatever test code in here
printf("Testing Server Guild Rules values:\n");
printf("AutoJoin: %i\n", rule_manager.GetGlobalRule(R_World, GuildAutoJoin)->GetInt8());
printf("Guild ID: %i\n", rule_manager.GetGlobalRule(R_World, GuildAutoJoinID)->GetInt32());
printf("Rank: %i\n", rule_manager.GetGlobalRule(R_World, GuildAutoJoinDefaultRankID)->GetInt8());
return true;
}

View File

@ -0,0 +1,62 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _CONSOLECOMMANDS_H
#define _CONSOLECOMMANDS_H
#include "../../common/seperator.h"
struct ConsoleCommand
{
bool(*CommandPointer)(Seperator *);
const char * Name; // 10 chars
const char * ParameterFormat; // 30 chars
const char * Description; // 40 chars
// = 70 chars
};
void ProcessConsoleInput(const char * command);
bool ConsoleBanCommand(Seperator *sep);
bool ConsoleUnbanCommand(Seperator *sep);
bool ConsoleKickCommand(Seperator *sep);
bool ConsoleAnnounceCommand(Seperator *sep);
bool ConsoleBroadcastCommand(Seperator *sep);
bool ConsoleChannelCommand(Seperator *sep);
bool ConsoleTellCommand(Seperator *sep);
bool ConsoleGuildCommand(Seperator *sep);
bool ConsolePlayerCommand(Seperator *sep);
bool ConsoleSetAdminPlayer(Seperator *sep);
bool ConsoleWorldCommand(Seperator *sep);
bool ConsoleZoneCommand(Seperator *sep);
bool ConsoleGetMOTDCommand(Seperator *sep);
bool ConsoleSetMOTDCommand(Seperator *sep);
bool ConsoleWhoCommand(Seperator *sep);
bool ConsoleReloadCommand(Seperator *sep);
bool ConsoleShutdownCommand(Seperator *sep);
bool ConsoleCancelShutdownCommand(Seperator *sep);
bool ConsoleExitCommand(Seperator *sep);
bool ConsoleRulesCommand(Seperator *sep);
bool ConsoleTestCommand(Seperator *sep);
#endif

4048
old/WorldServer/Entity.cpp Normal file

File diff suppressed because it is too large Load Diff

2169
old/WorldServer/Entity.h Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,183 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Factions.h"
#include "client.h"
extern MasterFactionList master_faction_list;
extern ConfigReader configReader;
PlayerFaction::PlayerFaction(){
MFactionUpdateNeeded.SetName("PlayerFaction::MFactionUpdateNeeded");
}
sint32 PlayerFaction::GetMaxValue(sint8 con){
if(con < 0)
return con * 10000;
else
return (con * 10000) + 9999;
}
sint32 PlayerFaction::GetMinValue(sint8 con){
if(con <= 0)
return (con * 10000) - 9999;
else
return (con * 10000);
}
bool PlayerFaction::ShouldAttack(int32 faction_id){
return (GetCon(faction_id) <= -4);
}
sint8 PlayerFaction::GetCon(int32 faction_id){
if(faction_id <= 10){
if(faction_id == 0)
return 0;
return (faction_id-5);
}
sint32 value = GetFactionValue(faction_id);
if(value >= -9999 && value <= 9999)
return 0;
else{
if(value>= 40000)
return 4;
else if(value <= -40000)
return -4;
return (sint8)(value/10000);
}
}
int8 PlayerFaction::GetPercent(int32 faction_id){
if(faction_id <= 10)
return 0;
sint8 con = GetCon(faction_id);
sint32 value = GetFactionValue(faction_id);
if(con != 0){
if(value <= 0)
value *= -1;
if(con < 0)
con *= -1;
value -= con * 10000;
value *= 100;
return value / 10000;
}
else{
value += 10000;
value *= 100;
return value / 20000;
}
}
EQ2Packet* PlayerFaction::FactionUpdate(int16 version){
EQ2Packet* ret = 0;
Faction* faction = 0;
PacketStruct* packet = configReader.getStruct("WS_FactionUpdate", version);
MFactionUpdateNeeded.lock();
if(packet){
packet->setArrayLengthByName("num_factions", faction_update_needed.size());
for(int32 i=0;i<faction_update_needed.size();i++){
faction = master_faction_list.GetFaction(faction_update_needed[i]);
if(faction){
packet->setArrayDataByName("faction_id", faction->id, i);
packet->setArrayDataByName("name", faction->name.c_str(), i);
packet->setArrayDataByName("description", faction->description.c_str(), i);
packet->setArrayDataByName("category", faction->type.c_str(), i);
packet->setArrayDataByName("con", GetCon(faction->id), i);
packet->setArrayDataByName("percentage", GetPercent(faction->id), i);
packet->setArrayDataByName("value", GetFactionValue(faction->id), i);
}
}
ret = packet->serialize();
safe_delete(packet);
}
faction_update_needed.clear();
MFactionUpdateNeeded.unlock();
return ret;
}
sint32 PlayerFaction::GetFactionValue(int32 faction_id){
if(faction_id <= 10)
return 0;
//devn00b: This always seems to return 1, even if the player infact has no faction. since we handle this via a check in zoneserver.cpp (processfaction)
//if(faction_values.count(faction_id) == 0)
//return master_faction_list.GetDefaultFactionValue(faction_id); //faction_values[faction_id] = master_faction_list.GetDefaultFactionValue(faction_id);
return faction_values[faction_id];
}
bool PlayerFaction::ShouldIncrease(int32 faction_id){
if(faction_id <= 10 || master_faction_list.GetIncreaseAmount(faction_id) == 0)
return false;
return true;
}
bool PlayerFaction::ShouldDecrease(int32 faction_id){
if(faction_id <= 10 || master_faction_list.GetDecreaseAmount(faction_id) == 0)
return false;
return true;
}
bool PlayerFaction::IncreaseFaction(int32 faction_id, int32 amount){
if(faction_id <= 10)
return true;
bool ret = true;
if(amount == 0)
amount = master_faction_list.GetIncreaseAmount(faction_id);
faction_values[faction_id] += amount;
if(faction_values[faction_id] >= 50000){
faction_values[faction_id] = 50000;
ret = false;
}
MFactionUpdateNeeded.lock();
faction_update_needed.push_back(faction_id);
MFactionUpdateNeeded.unlock();
return ret;
}
bool PlayerFaction::DecreaseFaction(int32 faction_id, int32 amount){
if(faction_id <= 10)
return true;
bool ret = true;
if(amount == 0)
amount = master_faction_list.GetDecreaseAmount(faction_id);
if(amount != 0){
faction_values[faction_id] -= amount;
if(faction_values[faction_id] <= -50000){
faction_values[faction_id] = -50000;
ret = false;
}
}
else
ret = false;
MFactionUpdateNeeded.lock();
faction_update_needed.push_back(faction_id);
MFactionUpdateNeeded.unlock();
return ret;
}
bool PlayerFaction::SetFactionValue(int32 faction_id, sint32 value){
faction_values[faction_id] = value;
MFactionUpdateNeeded.lock();
faction_update_needed.push_back(faction_id);
MFactionUpdateNeeded.unlock();
return true;
}

138
old/WorldServer/Factions.h Normal file
View File

@ -0,0 +1,138 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef EQ2_FACTIONS
#define EQ2_FACTIONS
#include "../common/config_reader.hpp"
struct Faction {
int32 id;
string name;
string type;
string description;
int16 negative_change;
int16 positive_change;
sint32 default_value;
};
class MasterFactionList{
public:
MasterFactionList(){
}
~MasterFactionList(){
Clear();
}
void Clear() {
map<int32,Faction*>::iterator iter;
for(iter = global_faction_list.begin();iter != global_faction_list.end(); iter++){
safe_delete(iter->second);
}
hostile_factions.clear();
friendly_factions.clear();
}
sint32 GetDefaultFactionValue(int32 faction_id){
if(global_faction_list.count(faction_id) > 0 && global_faction_list[faction_id])
return global_faction_list[faction_id]->default_value;
return 0;
}
Faction* GetFaction(char* name){
return faction_name_list[name];
}
Faction* GetFaction(int32 id){
if(global_faction_list.count(id) > 0)
return global_faction_list[id];
return 0;
}
void AddFaction(Faction* faction){
global_faction_list[faction->id] = faction;
faction_name_list[faction->name] = faction;
}
sint32 GetIncreaseAmount(int32 faction_id){
if(global_faction_list.count(faction_id) > 0 && global_faction_list[faction_id])
return global_faction_list[faction_id]->positive_change;
return 0;
}
sint32 GetDecreaseAmount(int32 faction_id){
if(global_faction_list.count(faction_id) > 0 && global_faction_list[faction_id])
return global_faction_list[faction_id]->negative_change;
return 0;
}
int32 GetFactionCount(){
return global_faction_list.size();
}
void AddHostileFaction(int32 faction_id, int32 hostile_faction_id){
hostile_factions[faction_id].push_back(hostile_faction_id);
}
void AddFriendlyFaction(int32 faction_id, int32 friendly_faction_id){
friendly_factions[faction_id].push_back(friendly_faction_id);
}
vector<int32>* GetFriendlyFactions(int32 faction_id){
if(friendly_factions.count(faction_id) > 0)
return &friendly_factions[faction_id];
else
return 0;
}
vector<int32>* GetHostileFactions(int32 faction_id){
if(hostile_factions.count(faction_id) > 0)
return &hostile_factions[faction_id];
else
return 0;
}
const char* GetFactionNameByID(int32 faction_id) {
if (faction_id > 0 && global_faction_list.count(faction_id) > 0)
return global_faction_list[faction_id]->name.c_str();
return 0;
}
private:
map<int32, vector<int32> > friendly_factions;
map<int32, vector<int32> > hostile_factions;
map<int32,Faction*> global_faction_list;
map<string,Faction*> faction_name_list;
};
class PlayerFaction{
public:
PlayerFaction();
sint32 GetMaxValue(sint8 con);
sint32 GetMinValue(sint8 con);
EQ2Packet* FactionUpdate(int16 version);
sint32 GetFactionValue(int32 faction_id);
bool ShouldIncrease(int32 faction_id);
bool ShouldDecrease(int32 faction_id);
bool IncreaseFaction(int32 faction_id, int32 amount = 0);
bool DecreaseFaction(int32 faction_id, int32 amount = 0);
bool SetFactionValue(int32 faction_id, sint32 value);
sint8 GetCon(int32 faction_id);
int8 GetPercent(int32 faction_id);
map<int32, sint32>* GetFactionValues(){
return &faction_values;
}
bool ShouldAttack(int32 faction_id);
private:
Mutex MFactionUpdateNeeded;
vector<int32> faction_update_needed;
map<int32, sint32> faction_values;
map<int32, int8> faction_percent;
};
#endif

View File

@ -0,0 +1,582 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "GroundSpawn.h"
#include "World.h"
#include "Spells.h"
#include "Rules/Rules.h"
#include "../common/misc_functions.hpp"
#include "../common/log.hpp"
extern ConfigReader configReader;
extern MasterSpellList master_spell_list;
extern World world;
extern RuleManager rule_manager;
GroundSpawn::GroundSpawn(){
packet_num = 0;
appearance.difficulty = 0;
spawn_type = 2;
appearance.pos.state = 129;
number_harvests = 0;
num_attempts_per_harvest = 0;
groundspawn_id = 0;
MHarvest.SetName("GroundSpawn::MHarvest");
MHarvestUse.SetName("GroundSpawn::MHarvestUse");
randomize_heading = true; // we by default randomize heading of groundspawns DB overrides
}
GroundSpawn::~GroundSpawn(){
}
EQ2Packet* GroundSpawn::serialize(Player* player, int16 version){
return spawn_serialize(player, version);
}
int8 GroundSpawn::GetNumberHarvests(){
return number_harvests;
}
void GroundSpawn::SetNumberHarvests(int8 val){
number_harvests = val;
}
int8 GroundSpawn::GetAttemptsPerHarvest(){
return num_attempts_per_harvest;
}
void GroundSpawn::SetAttemptsPerHarvest(int8 val){
num_attempts_per_harvest = val;
}
int32 GroundSpawn::GetGroundSpawnEntryID(){
return groundspawn_id;
}
void GroundSpawn::SetGroundSpawnEntryID(int32 val){
groundspawn_id = val;
}
void GroundSpawn::SetCollectionSkill(const char* val){
if(val)
collection_skill = string(val);
}
const char* GroundSpawn::GetCollectionSkill(){
return collection_skill.c_str();
}
void GroundSpawn::ProcessHarvest(Client* client) {
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Process harvesting for player '%s' (%u)", client->GetPlayer()->GetName(), client->GetPlayer()->GetID());
MHarvest.lock();
vector<GroundSpawnEntry*>* groundspawn_entries = GetZone()->GetGroundSpawnEntries(groundspawn_id);
vector<GroundSpawnEntryItem*>* groundspawn_items = GetZone()->GetGroundSpawnEntryItems(groundspawn_id);
Item* master_item = 0;
Item* master_rare = 0;
Item* item = 0;
Item* item_rare = 0;
int16 lowest_skill_level = 0;
int16 table_choice = 0;
int32 item_choice = 0;
int32 rare_choice = 0;
int8 harvest_type = 0;
int32 item_harvested = 0;
int8 reward_total = 1;
int32 rare_harvested = 0;
int8 rare_item = 0;
bool is_collection = false;
if (!groundspawn_entries || !groundspawn_items) {
LogWrite(GROUNDSPAWN__ERROR, 3, "GSpawn", "No groundspawn entries or items assigned to groundspawn id: %u", groundspawn_id);
client->Message(CHANNEL_COLOR_RED, "Error: There are no groundspawn entries or items assigned to groundspawn id: %u", groundspawn_id);
MHarvest.unlock();
return;
}
if (number_harvests == 0) {
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Total harvests depleated for groundspawn id: %u", groundspawn_id);
client->SimpleMessage(CHANNEL_COLOR_RED, "Error: This spawn has nothing more to harvest!");
MHarvest.unlock();
return;
}
Skill* skill = 0;
if (collection_skill == "Collecting") {
skill = client->GetPlayer()->GetSkillByName("Gathering");
is_collection = true;
}
else
skill = client->GetPlayer()->GetSkillByName(collection_skill.c_str()); // Fix: #576 - don't skill up yet with GetSkillByName(skill, true), we might be trying to harvest low level
if (!skill) {
LogWrite(GROUNDSPAWN__WARNING, 3, "GSpawn", "Player '%s' lacks the skill: '%s'", client->GetPlayer()->GetName(), collection_skill.c_str());
client->Message(CHANNEL_COLOR_RED, "Error: You do not have the '%s' skill!", collection_skill.c_str());
MHarvest.unlock();
return;
}
int16 totalSkill = skill->current_val;
int32 skillID = master_item_list.GetItemStatIDByName(collection_skill);
int16 max_skill_req_groundspawn = rule_manager.GetZoneRule(client->GetCurrentZoneID(), R_Player, MinSkillMultiplierValue)->GetInt16();
if(max_skill_req_groundspawn < 1) // can't be 0
max_skill_req_groundspawn = 1;
if(skillID != 0xFFFFFFFF)
{
((Entity*)client->GetPlayer())->MStats.lock();
totalSkill += ((Entity*)client->GetPlayer())->stats[skillID];
((Entity*)client->GetPlayer())->MStats.unlock();
}
for (int8 i = 0; i < num_attempts_per_harvest; i++) {
vector<GroundSpawnEntry*> mod_groundspawn_entries;
if (groundspawn_entries) {
vector<GroundSpawnEntry*> highest_match;
vector<GroundSpawnEntry*>::iterator itr;
GroundSpawnEntry* entry = 0; // current data
GroundSpawnEntry* selected_table = 0; // selected table data
// first, iterate through groundspawn_entries, discard tables player cannot use
for (itr = groundspawn_entries->begin(); itr != groundspawn_entries->end(); itr++) {
entry = *itr;
if(entry->min_skill_level > max_skill_req_groundspawn)
max_skill_req_groundspawn = entry->min_skill_level;
// if player lacks skill, skip table
if (entry->min_skill_level > totalSkill)
continue;
// if bonus, but player lacks level, skip table
if (entry->bonus_table && (client->GetPlayer()->GetLevel() < entry->min_adventure_level))
continue;
// build modified entries table
mod_groundspawn_entries.push_back(entry);
LogWrite(GROUNDSPAWN__DEBUG, 5, "GSpawn", "Keeping groundspawn_entry: %i", entry->min_skill_level);
}
// if anything remains, find lowest min_skill_level in remaining set(s)
if (mod_groundspawn_entries.size() > 0) {
vector<GroundSpawnEntry*>::iterator itr;
GroundSpawnEntry* entry = 0;
for (itr = mod_groundspawn_entries.begin(); itr != mod_groundspawn_entries.end(); itr++) {
entry = *itr;
// find the low range of available tables for random roll
if (lowest_skill_level > entry->min_skill_level || lowest_skill_level == 0)
lowest_skill_level = entry->min_skill_level;
}
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Lowest Skill Level: %i", lowest_skill_level);
}
else {
// if no tables chosen, you must lack the skills
// TODO: move this check to LUA when harvest command is first selected
client->Message(CHANNEL_COLOR_RED, "You lack the skills to harvest this node!");
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "All groundspawn_entry tables tossed! No Skills? Something broke?");
MHarvest.unlock();
return;
}
// now roll to see which table to use
table_choice = MakeRandomInt(lowest_skill_level, totalSkill);
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Random INT for Table by skill level: %i", table_choice);
int16 highest_score = 0;
for (itr = mod_groundspawn_entries.begin(); itr != mod_groundspawn_entries.end(); itr++) {
entry = *itr;
// determines the highest min_skill_level in the current set of tables (if multiple tables)
if (table_choice >= entry->min_skill_level && (highest_score == 0 || highest_score < table_choice)) {
// removes old highest for the new one
highest_match.clear();
highest_score = entry->min_skill_level;
}
// if the score = level, push into highest_match set
if (highest_score == entry->min_skill_level)
highest_match.push_back(entry);
}
// if there is STILL more than 1 table player qualifies for, rand() and pick one
if (highest_match.size() > 1) {
int16 rand_index = rand() % highest_match.size();
selected_table = highest_match.at(rand_index);
}
else if (highest_match.size() > 0)
selected_table = highest_match.at(0);
// by this point, we should have 1 table who's min skill matches the score (selected_table)
if (selected_table) {
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Using Table: %i, %i, %i, %.2f, %.2f, %.2f, %.2f, %.2f, %.2f, %i",
selected_table->min_skill_level,
selected_table->min_adventure_level,
selected_table->bonus_table,
selected_table->harvest1,
selected_table->harvest3,
selected_table->harvest5,
selected_table->harvest_imbue,
selected_table->harvest_rare,
selected_table->harvest10,
selected_table->harvest_coin);
// roll 1-100 for chance-to-harvest percentage
float chance = MakeRandomFloat(0, 100);
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Random FLOAT for harvest percentages: %.2f", chance);
// starting with the lowest %, select a harvest type + reward qty
if (chance <= selected_table->harvest10 && is_collection == false) {
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest 10 items + Rare Item from table : %i", selected_table->min_skill_level);
harvest_type = 6;
reward_total = 10;
}
else if (chance <= selected_table->harvest_rare && is_collection == false) {
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest Rare Item from table : %i", selected_table->min_skill_level);
harvest_type = 5;
}
else if (chance <= selected_table->harvest_imbue && is_collection == false) {
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest Imbue Item from table : %i", selected_table->min_skill_level);
harvest_type = 4;
}
else if (chance <= selected_table->harvest5 && is_collection == false) {
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest 5 Items from table : %i", selected_table->min_skill_level);
harvest_type = 3;
reward_total = 5;
}
else if (chance <= selected_table->harvest3 && is_collection == false) {
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest 3 Items from table : %i", selected_table->min_skill_level);
harvest_type = 2;
reward_total = 3;
}
else if (chance <= selected_table->harvest1 || totalSkill >= skill->max_val || is_collection) {
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest 1 Item from table : %i", selected_table->min_skill_level);
harvest_type = 1;
}
else
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Harvest nothing...");
float node_maxskill_multiplier = rule_manager.GetZoneRule(client->GetCurrentZoneID(), R_Player, HarvestSkillUpMultiplier)->GetFloat();
if(node_maxskill_multiplier <= 0.0f) {
node_maxskill_multiplier = 1.0f;
}
int16 skillup_max_skill_allowed = (int16)((float)max_skill_req_groundspawn*node_maxskill_multiplier);
if (!is_collection && skill && skill->current_val < skillup_max_skill_allowed) {
skill = client->GetPlayer()->GetSkillByName(collection_skill.c_str(), true); // Fix: #576 - skill up after min skill and adv level checks
}
}
// once you know how many and what type of item to harvest, pick an item from the list
if (harvest_type) {
vector<GroundSpawnEntryItem*> mod_groundspawn_items;
vector<GroundSpawnEntryItem*> mod_groundspawn_rares;
vector<GroundSpawnEntryItem*> mod_groundspawn_imbue;
vector<GroundSpawnEntryItem*>::iterator itr;
GroundSpawnEntryItem* entry = 0;
// iterate through groundspawn_items, discard items player cannot roll for
for (itr = groundspawn_items->begin(); itr != groundspawn_items->end(); itr++) {
entry = *itr;
// if this is a Rare, or an Imbue, but is_rare flag is 0, skip item
if ((harvest_type == 5 || harvest_type == 4) && entry->is_rare == 0)
continue;
// if it is a 1, 3, or 5 and is_rare = 1, skip
else if (harvest_type < 4 && entry->is_rare == 1)
continue;
// if the grid_id on the item matches player grid, or is 0, keep the item
if (!entry->grid_id || (entry->grid_id == client->GetPlayer()->GetLocation())) {
// build modified entries table
if ((entry->is_rare == 1 && harvest_type == 5) || (entry->is_rare == 1 && harvest_type == 6)) {
// if the matching item is rare, or harvest10 push to mod rares
mod_groundspawn_rares.push_back(entry);
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Keeping groundspawn_rare_item: %u", entry->item_id);
}
if (entry->is_rare == 0 && harvest_type != 4 && harvest_type != 5) {
// if the matching item is normal,or harvest 10 push to mod items
mod_groundspawn_items.push_back(entry);
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Keeping groundspawn_common_item: %u", entry->item_id);
}
if (entry->is_rare == 2 && harvest_type == 4) {
// if the matching item is imbue item, push to mod imbue
mod_groundspawn_imbue.push_back(entry);
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Keeping groundspawn_imbue_item: %u", entry->item_id);
}
}
}
// if any items remain in the list, random to see which one gets awarded
if (mod_groundspawn_items.size() > 0) {
// roll to see which item index to use
item_choice = rand() % mod_groundspawn_items.size();
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Random INT for which item to award: %i", item_choice);
// set item_id to be awarded
item_harvested = mod_groundspawn_items[item_choice]->item_id;
// if reward is rare, set flag
rare_item = mod_groundspawn_items[item_choice]->is_rare;
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Item ID to award: %u, Rare = %i", item_harvested, item_rare);
// if 10+rare, handle additional "rare" reward
if (harvest_type == 6) {
// make sure there is a rare table to choose from!
if (mod_groundspawn_rares.size() > 0) {
// roll to see which rare index to use
rare_choice = rand() % mod_groundspawn_rares.size();
// set (rare) item_id to be awarded
rare_harvested = mod_groundspawn_rares[rare_choice]->item_id;
// we're picking a rare here, so obviously this is true ;)
rare_item = 1;
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "RARE Item ID to award: %u", rare_harvested);
}
else {
// all rare entries were eliminated above, or none are assigned. Either way, shouldn't be here!
LogWrite(GROUNDSPAWN__ERROR, 3, "GSpawn", "Groundspawn Entry for '%s' (%i) has no RARE items!", GetName(), GetID());
}
}
}
else if (mod_groundspawn_rares.size() > 0) {
// roll to see which rare index to use
item_choice = rand() % mod_groundspawn_rares.size();
// set (rare) item_id to be awarded
item_harvested = mod_groundspawn_rares[item_choice]->item_id;
// we're picking a rare here, so obviously this is true ;)
rare_item = 1;
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "RARE Item ID to award: %u", rare_harvested);
}
else if (mod_groundspawn_imbue.size() > 0) {
// roll to see which rare index to use
item_choice = rand() % mod_groundspawn_imbue.size();
// set (rare) item_id to be awarded
item_harvested = mod_groundspawn_imbue[item_choice]->item_id;
// we're picking a rare here, so obviously this is true ;)
rare_item = 0;
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "imbue Item ID to award: %u", rare_harvested);
}
else {
// all item entries were eliminated above, or none are assigned. Either way, shouldn't be here!
LogWrite(GROUNDSPAWN__ERROR, 0, "GSpawn", "Groundspawn Entry for '%s' (%i) has no items!", GetName(), GetID());
}
// if an item was harvested, send updates to client, add item to inventory
if (item_harvested) {
char tmp[200] = { 0 };
// set Normal item harvested
master_item = master_item_list.GetItem(item_harvested);
if (master_item) {
// set details of Normal item
item = new Item(master_item);
// set how many of this item the player receives
item->details.count = reward_total;
// chat box update for normal item (todo: verify output text)
client->Message(CHANNEL_HARVESTING, "You %s %i %s from the %s.", GetHarvestMessageName(true).c_str(), item->details.count, item->CreateItemLink(client->GetVersion(), true).c_str(), GetName());
// add Normal item to player inventory
bool itemDeleted = false;
client->AddItem(item, &itemDeleted);
if(!itemDeleted) {
//Check if the player has a harvesting quest for this
client->GetPlayer()->CheckQuestsHarvestUpdate(item, reward_total);
// if this is a 10+rare, handle sepErately
if (harvest_type == 6 && rare_item == 1) {
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Item ID %u is Normal. Qty %i", item_harvested, item->details.count);
// send Normal harvest message to client
sprintf(tmp, "\\#64FFFFYou have %s:\12\\#C8FFFF%i %s", GetHarvestMessageName().c_str(), item->details.count, item->name.c_str());
client->SendPopupMessage(10, tmp, "ui_harvested_normal", 2.25, 0xFF, 0xFF, 0xFF);
client->GetPlayer()->UpdatePlayerStatistic(STAT_PLAYER_ITEMS_HARVESTED, item->details.count);
// set Rare item harvested
master_rare = master_item_list.GetItem(rare_harvested);
if (master_rare) {
// set details of Rare item
item_rare = new Item(master_rare);
// count of Rare is always 1
item_rare->details.count = 1;
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Item ID %u is RARE!", rare_harvested);
// send Rare harvest message to client
sprintf(tmp, "\\#FFFF6ERare item found!\12%s: \\#C8FFFF%i %s", GetHarvestMessageName().c_str(), item_rare->details.count, item_rare->name.c_str());
client->Message(CHANNEL_HARVESTING, "You have found a rare item!");
client->SendPopupMessage(11, tmp, "ui_harvested_rare", 2.25, 0xFF, 0xFF, 0xFF);
client->GetPlayer()->UpdatePlayerStatistic(STAT_PLAYER_RARES_HARVESTED, item_rare->details.count);
// chat box update for rare item (todo: verify output text)
client->Message(CHANNEL_HARVESTING, "You %s %i %s from the %s.", GetHarvestMessageName(true).c_str(), item_rare->details.count, item->CreateItemLink(client->GetVersion(), true).c_str(), GetName());
// add Rare item to player inventory
client->AddItem(item_rare);
//Check if the player has a harvesting quest for this
client->GetPlayer()->CheckQuestsHarvestUpdate(item_rare, 1);
}
}
else if (rare_item == 1) {
// if harvest signaled rare or imbue type
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Item ID %u is RARE! Qty: %i", item_harvested, item->details.count);
// send Rare harvest message to client
sprintf(tmp, "\\#FFFF6ERare item found!\12%s: \\#C8FFFF%i %s", GetHarvestMessageName().c_str(), item->details.count, item->name.c_str());
client->Message(CHANNEL_HARVESTING, "You have found a rare item!");
client->SendPopupMessage(11, tmp, "ui_harvested_rare", 2.25, 0xFF, 0xFF, 0xFF);
client->GetPlayer()->UpdatePlayerStatistic(STAT_PLAYER_RARES_HARVESTED, item->details.count);
}
else {
// send Normal harvest message to client
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "Item ID %u is Normal. Qty %i", item_harvested, item->details.count);
sprintf(tmp, "\\#64FFFFYou have %s:\12\\#C8FFFF%i %s", GetHarvestMessageName().c_str(), item->details.count, item->name.c_str());
client->SendPopupMessage(10, tmp, "ui_harvested_normal", 2.25, 0xFF, 0xFF, 0xFF);
client->GetPlayer()->UpdatePlayerStatistic(STAT_PLAYER_ITEMS_HARVESTED, item->details.count);
}
}
}
else {
// error!
LogWrite(GROUNDSPAWN__ERROR, 0, "GSpawn", "Error: Item ID Not Found - %u", item_harvested);
client->Message(CHANNEL_COLOR_RED, "Error: Unable to find item id %u", item_harvested);
}
// decrement # of pulls on this node before it despawns
number_harvests--;
}
else {
// if no item harvested
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "No item_harvested");
client->Message(CHANNEL_HARVESTING, "You failed to %s anything from %s.", GetHarvestMessageName(true, true).c_str(), GetName());
}
}
else {
// if no harvest type
LogWrite(GROUNDSPAWN__DEBUG, 3, "GSpawn", "No harvest_type");
client->Message(CHANNEL_HARVESTING, "You failed to %s anything from %s.", GetHarvestMessageName(true, true).c_str(), GetName());
}
}
} // cycle through num_attempts_per_harvest
MHarvest.unlock();
LogWrite(GROUNDSPAWN__DEBUG, 0, "GSpawn", "Process harvest complete for player '%s' (%u)", client->GetPlayer()->GetName(), client->GetPlayer()->GetID());
}
string GroundSpawn::GetHarvestMessageName(bool present_tense, bool failure){
string ret = "";
if((collection_skill == "Gathering" ||collection_skill == "Collecting") && !present_tense)
ret = "gathered";
else if(collection_skill == "Gathering" || collection_skill == "Collecting")
ret = "gather";
else if(collection_skill == "Mining" && !present_tense)
ret = "mined";
else if(collection_skill == "Mining")
ret = "mine";
else if (collection_skill == "Fishing" && !present_tense)
ret = "fished";
else if(collection_skill == "Fishing")
ret = "fish";
else if(collection_skill == "Trapping" && !present_tense && !failure)
ret = "acquired";
else if(collection_skill == "Trapping" && failure)
ret = "trap";
else if(collection_skill == "Trapping")
ret = "acquire";
else if(collection_skill == "Foresting" && !present_tense)
ret = "forested";
else if(collection_skill == "Foresting")
ret = "forest";
else if (collection_skill == "Collecting")
ret = "collect";
return ret;
}
string GroundSpawn::GetHarvestSpellType(){
string ret = "";
if(collection_skill == "Gathering" || collection_skill == "Collecting")
ret = "gather";
else if(collection_skill == "Mining")
ret = "mine";
else if(collection_skill == "Trapping")
ret = "trap";
else if(collection_skill == "Foresting")
ret = "chop";
else if(collection_skill == "Fishing")
ret = "fish";
return ret;
}
string GroundSpawn::GetHarvestSpellName() {
string ret = "";
if (collection_skill == "Collecting")
ret = "Gathering";
else
ret = collection_skill;
return ret;
}
void GroundSpawn::HandleUse(Client* client, string type){
if(!client || (client->GetVersion() > 561 && type.length() == 0)) // older clients do not send the type
return;
//The following check disables the use of the groundspawn if spawn access is not granted
if (client) {
bool meets_quest_reqs = MeetsSpawnAccessRequirements(client->GetPlayer());
if (!meets_quest_reqs && (GetQuestsRequiredOverride() & 2) == 0)
return;
else if (meets_quest_reqs && appearance.show_command_icon != 1)
return;
}
MHarvestUse.lock();
std::string typeLwr = ToLower(type);
if(client->GetVersion() <= 561 && (typeLwr == "" || typeLwr == "collect" || typeLwr == "gather" || typeLwr == "chop" || typeLwr == "mine"))
type = GetHarvestSpellType();
if (type == GetHarvestSpellType() && MeetsSpawnAccessRequirements(client->GetPlayer())) {
Spell* spell = master_spell_list.GetSpellByName(GetHarvestSpellName().c_str());
if (spell)
client->GetCurrentZone()->ProcessSpell(spell, client->GetPlayer(), client->GetPlayer()->GetTarget(), true, true);
}
else if (appearance.show_command_icon == 1 && MeetsSpawnAccessRequirements(client->GetPlayer())) {
EntityCommand* entity_command = FindEntityCommand(type);
if (entity_command)
client->GetCurrentZone()->ProcessEntityCommand(entity_command, client->GetPlayer(), client->GetPlayer()->GetTarget());
}
MHarvestUse.unlock();
}

View File

@ -0,0 +1,86 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __EQ2_GroundSpawn__
#define __EQ2_GroundSpawn__
#include "Spawn.h"
#include "client.h"
class GroundSpawn : public Spawn {
public:
GroundSpawn();
virtual ~GroundSpawn();
GroundSpawn* Copy(){
GroundSpawn* new_spawn = new GroundSpawn();
new_spawn->size = size;
new_spawn->SetPrimaryCommands(&primary_command_list);
new_spawn->SetSecondaryCommands(&secondary_command_list);
new_spawn->database_id = database_id;
new_spawn->primary_command_list_id = primary_command_list_id;
new_spawn->secondary_command_list_id = secondary_command_list_id;
memcpy(&new_spawn->appearance, &appearance, sizeof(AppearanceData));
new_spawn->faction_id = faction_id;
new_spawn->target = 0;
new_spawn->SetTotalHP(GetTotalHP());
new_spawn->SetTotalPower(GetTotalPower());
new_spawn->SetHP(GetHP());
new_spawn->SetPower(GetPower());
new_spawn->SetNumberHarvests(number_harvests);
new_spawn->SetAttemptsPerHarvest(num_attempts_per_harvest);
new_spawn->SetGroundSpawnEntryID(groundspawn_id);
new_spawn->SetCollectionSkill(collection_skill.c_str());
SetQuestsRequired(new_spawn);
new_spawn->forceMapCheck = forceMapCheck;
new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag());
new_spawn->SetLootTier(GetLootTier());
new_spawn->SetLootDropType(GetLootDropType());
new_spawn->SetRandomizeHeading(GetRandomizeHeading());
return new_spawn;
}
bool IsGroundSpawn(){ return true; }
EQ2Packet* serialize(Player* player, int16 version);
int8 GetNumberHarvests();
void SetNumberHarvests(int8 val);
int8 GetAttemptsPerHarvest();
void SetAttemptsPerHarvest(int8 val);
int32 GetGroundSpawnEntryID();
void SetGroundSpawnEntryID(int32 val);
void ProcessHarvest(Client* client);
void SetCollectionSkill(const char* val);
const char* GetCollectionSkill();
string GetHarvestMessageName(bool present_tense = false, bool failure = false);
string GetHarvestSpellType();
string GetHarvestSpellName();
void HandleUse(Client* client, string type);
void SetRandomizeHeading(bool val) { randomize_heading = val; }
bool GetRandomizeHeading() { return randomize_heading; }
private:
int8 number_harvests;
int8 num_attempts_per_harvest;
int32 groundspawn_id;
string collection_skill;
Mutex MHarvest;
Mutex MHarvestUse;
bool randomize_heading;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,453 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef GUILD_H_
#define GUILD_H_
#include <vector>
#include <deque>
#include <map>
#include "../MutexMap.h"
using namespace std;
class ZoneServer;
class Client;
class Player;
#define GUILD_RANK_LEADER 0
#define GUILD_RANK_SENIOR_OFFICER 1
#define GUILD_RANK_OFFICER 2
#define GUILD_RANK_SENIOR_MEMBER 3
#define GUILD_RANK_MEMBER 4
#define GUILD_RANK_JUNIOR_MEMBER 5
#define GUILD_RANK_INITIATE 6
#define GUILD_RANK_RECRUIT 7
#define GUILD_PERMISSIONS_INVITE 0
#define GUILD_PERMISSIONS_RMEOVE_MEMBER 1
#define GUILD_PERMISSIONS_PROMOTE_MEMBER 2
#define GUILD_PERMISSIONS_DEMOTE_MEMBER 3
#define GUILD_PERMISSIONS_CHANGE_MOTD 6
#define GUILD_PERMISSIONS_CHANGE_PERMISSIONS 7
#define GUILD_PERMISSIONS_CHANGE_RANK_NAMES 8
#define GUILD_PERMISSIONS_SEE_OFFICER_NOTES 9
#define GUILD_PERMISSIONS_EDIT_OFFICER_NOTES 10
#define GUILD_PERMISSIONS_SEE_OFFICER_CHAT 11
#define GUILD_PERMISSIONS_SPEAK_IN_OFFICER_CHAT 12
#define GUILD_PERMISSIONS_SEE_GUILD_CHAT 13
#define GUILD_PERMISSIONS_SPEAK_IN_GUILD_CHAT 14
#define GUILD_PERMISSIONS_EDIT_PERSONAL_NOTES 15
#define GUILD_PERMISSIONS_EDIT_PERSONAL_NOTES_OTHERS 16
#define GUILD_PERMISSIONS_EDIT_EVENT_FILTERS 17
#define GUILD_PERMISSIONS_EDIT_EVENTS 18
#define GUILD_PERMISSIONS_PURCHASE_STATUS_ITEMS 19
#define GUILD_PERMISSIONS_DISPLAY_GUILD_NAME 20
#define GUILD_PERMISSIONS_SEND_EMAIL_TO_GUILD 21
#define GUILD_PERMISSIONS_BANK1_SEE_CONTENTS 22
#define GUILD_PERMISSIONS_BANK2_SEE_CONTENTS 23
#define GUILD_PERMISSIONS_BANK3_SEE_CONTENTS 24
#define GUILD_PERMISSIONS_BANK4_SEE_CONTENTS 25
#define GUILD_PERMISSIONS_BANK1_DEPOSIT 26
#define GUILD_PERMISSIONS_BANK2_DEPOSIT 27
#define GUILD_PERMISSIONS_BANK3_DEPOSIT 28
#define GUILD_PERMISSIONS_BANK4_DEPOSIT 29
#define GUILD_PERMISSIONS_BANK1_WITHDRAWL 30
#define GUILD_PERMISSIONS_BANK2_WITHDRAWL 31
#define GUILD_PERMISSIONS_BANK3_WITHDRAWL 32
#define GUILD_PERMISSIONS_BANK4_WITHDRAWL 33
#define GUILD_PERMISSIONS_EDIT_RECRUITING_SETTINGS 35
#define GUILD_PERMISSIONS_MAKE_OTHERS_RECRUITERS 36
#define GUILD_PERMISSIONS_SEE_RECRUITING_SETTINGS 37
#define GUILD_PERMISSIONS_ASSIGN_POINTS 43
#define GUILD_PERMISSIONS_RECEIVE_POINTS 44
#define GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY 0
#define GUILD_EVENT_FILTER_CATEGORY_BROADCAST 1
#define GUILD_EVENT_GUILD_LEVEL_UP 0
#define GUILD_EVENT_GUILD_LEVEL_DOWN 1
#define GUILD_EVENT_DISCOVERS_ITEM 2
#define GUILD_EVENT_GAINS_ADV_LEVEL_1_10 3
#define GUILD_EVENT_GAINS_ADV_LEVEL_11_20 4
#define GUILD_EVENT_GAINS_ADV_LEVEL_21_30 5
#define GUILD_EVENT_GAINS_ADV_LEVEL_31_40 6
#define GUILD_EVENT_GAINS_ADV_LEVEL_41_50 7
#define GUILD_EVENT_GAINS_TS_LEVEL_1_10 8
#define GUILD_EVENT_GAINS_TS_LEVEL_11_20 9
#define GUILD_EVENT_GAINS_TS_LEVEL_21_30 10
#define GUILD_EVENT_GAINS_TS_LEVEL_31_40 11
#define GUILD_EVENT_GAINS_TS_LEVEL_41_50 12
#define GUILD_EVENT_MEMBER_JOINS 13
#define GUILD_EVENT_MEMBER_LEAVES 14
#define GUILD_EVENT_MEMBER_PROMOTED 15
#define GUILD_EVENT_MEMBER_DEMOTED 16
#define GUILD_EVENT_COMPLETES_HERITAGE_QUEST 19
#define GUILD_EVENT_KILLS_EPIC_MONSTER 20
#define GUILD_EVENT_LOOTS_ARTIFACT 21
#define GUILD_EVENT_LOOTS_FABELED_ITEM 22
#define GUILD_EVENT_LOOTS_LEGENDARY_ITEM 23
#define GUILD_EVENT_COMPLETES_WRIT 24
#define GUILD_EVENT_LOOTS_MYTHICAL_ITEM 25
#define GUILD_EVENT_GAINS_ADV_LEVEL_10 26
#define GUILD_EVENT_GAINS_ADV_LEVEL_20 27
#define GUILD_EVENT_GAINS_ADV_LEVEL_30 28
#define GUILD_EVENT_GAINS_ADV_LEVEL_40 29
#define GUILD_EVENT_GAINS_ADV_LEVEL_50 30
#define GUILD_EVENT_GAINS_TS_LEVEL_10 31
#define GUILD_EVENT_GAINS_TS_LEVEL_20 32
#define GUILD_EVENT_GAINS_TS_LEVEL_30 33
#define GUILD_EVENT_GAINS_TS_LEVEL_40 34
#define GUILD_EVENT_GAINS_TS_LEVEL_50 35
#define GUILD_EVENT_GAINS_ADV_LEVEL_51_60 37
#define GUILD_EVENT_GAINS_TS_LEVEL_51_60 38
#define GUILD_EVENT_GAINS_ADV_LEVEL_60 39
#define GUILD_EVENT_GAINS_TS_LEVEL_60 40
#define GUILD_EVENT_GAINS_ADV_LEVEL_61_70 41
#define GUILD_EVENT_GAINS_TS_LEVEL_61_70 42
#define GUILD_EVENT_GAINS_ADV_LEVEL_70 43
#define GUILD_EVENT_GAINS_TS_LEVEL_70 44
#define GUILD_EVENT_GAINS_AA_10 45
#define GUILD_EVENT_GAINS_AA_20 46
#define GUILD_EVENT_GAINS_AA_30 47
#define GUILD_EVENT_GAINS_AA_40 48
#define GUILD_EVENT_GAINS_AA_50 49
#define GUILD_EVENT_GAINS_AA_1_10 50
#define GUILD_EVENT_GAINS_AA_11_20 51
#define GUILD_EVENT_GAINS_AA_21_30 52
#define GUILD_EVENT_GAINS_AA_31_40 53
#define GUILD_EVENT_GAINS_AA_41_50 54
#define GUILD_EVENT_BECOMES_RECRUITER 55
#define GUILD_EVENT_NO_LONGER_RECRUITER 56
#define GUILD_EVENT_HERALDY_CHANGE 57
#define GUILD_EVENT_GAINS_AA_60 58
#define GUILD_EVENT_GAINS_AA_70 59
#define GUILD_EVENT_GAINS_AA_80 60
#define GUILD_EVENT_GAINS_AA_90 61
#define GUILD_EVENT_GAINS_AA_100 62
#define GUILD_EVENT_GAINS_AA_51_60 63
#define GUILD_EVENT_GAINS_AA_61_70 64
#define GUILD_EVENT_GAINS_AA_71_80 65
#define GUILD_EVENT_GAINS_AA_81_90 66
#define GUILD_EVENT_GAINS_AA_91_100 67
#define GUILD_EVENT_GAINS_ADV_LEVEL_80 68
#define GUILD_EVENT_GAINS_TS_LEVEL_80 69
#define GUILD_EVENT_GAINS_ADV_LEVEL_71_80 70
#define GUILD_EVENT_GAINS_TS_LEVEL_71_80 71
#define GUILD_EVENT_GAINS_AA_110 72
#define GUILD_EVENT_GAINS_AA_120 73
#define GUILD_EVENT_GAINS_AA_130 74
#define GUILD_EVENT_GAINS_AA_140 75
#define GUILD_EVENT_GAINS_AA_101_110 76
#define GUILD_EVENT_GAINS_AA_111_120 77
#define GUILD_EVENT_GAINS_AA_121_130 78
#define GUILD_EVENT_GAINS_AA_131_140 79
#define GUILD_EVENT_GAINS_AA_150 80
#define GUILD_EVENT_GAINS_AA_141_150 81
#define GUILD_EVENT_GAINS_AA_160 82
#define GUILD_EVENT_GAINS_AA_170 83
#define GUILD_EVENT_GAINS_AA_180 84
#define GUILD_EVENT_GAINS_AA_190 85
#define GUILD_EVENT_GAINS_AA_200 86
#define GUILD_EVENT_GAINS_AA_151_160 87
#define GUILD_EVENT_GAINS_AA_161_170 88
#define GUILD_EVENT_GAINS_AA_171_180 89
#define GUILD_EVENT_GAINS_AA_181_190 90
#define GUILD_EVENT_GAINS_AA_191_200 91
#define GUILD_EVENT_EARNS_ACHIEVEMENT 92
#define GUILD_RECRUITING_FLAG_TRAINING 0
#define GUILD_RECRUITING_FLAG_FIGHTERS 1
#define GUILD_RECRUITING_FLAG_PRIESTS 2
#define GUILD_RECRUITING_FLAG_SCOUTS 3
#define GUILD_RECRUITING_FLAG_MAGES 4
#define GUILD_RECRUITING_FLAG_TRADESKILLERS 5
#define GUILD_RECRUITING_PLAYSTYLE_NONE 0
#define GUILD_RECRUITING_PLAYSTYLE_CASUAL 1
#define GUILD_RECRUITING_PLAYSTYLE_HARDCORE 2
#define GUILD_RECRUITING_DESC_TAG_NONE 0
#define GUILD_RECRUITING_DESC_TAG_GOOD 1
#define GUILD_RECRUITING_DESC_TAG_EVIL 2
#define GUILD_RECRUITING_DESC_TAG_CHATTY 3
#define GUILD_RECRUITING_DESC_TAG_ORGANIZED 4
#define GUILD_RECRUITING_DESC_TAG_ROLEPLAY 5
#define GUILD_RECRUITING_DESC_TAG_ENJOY_QUESTS 6
#define GUILD_RECRUITING_DESC_TAG_ENJOY_RAIDS 7
#define GUILD_RECRUITING_DESC_TAG_ODD_HOURS 8
#define GUILD_RECRUITING_DESC_TAG_CRAFTER_ORIENTED 9
#define GUILD_RECRUITING_DESC_TAG_FAMILY_FRIENDLY 10
#define GUILD_RECRUITING_DESC_TAG_MATURE_HUMOR 11
#define GUILD_RECRUITING_DESC_TAG_INMATES_RUN 12
#define GUILD_RECRUITING_DESC_TAG_VERY_FUNNY 13
#define GUILD_RECRUITING_DESC_TAG_HUMOR_CAUES_PAIN 14
#define GUILD_RECRUITING_DESC_TAG_SERIOUS 15
#define GUILD_MEMBER_FLAGS_RECRUITING_FOR_GUILD 1
#define GUILD_MEMBER_FLAGS_NOTIFY_LOGINS 2
#define GUILD_MEMBER_FLAGS_DONT_GENERATE_EVENTS 4
#define GUILD_EVENT_ACTION_LOCK 0
#define GUILD_EVENT_ACTION_UNLOCK 1
#define GUILD_EVENT_ACTION_DELETE 2
#define GUILD_MAX_LEVEL 80
#define GUILD_MAX_POINT_HISTORY 50
#define GUILD_MAX_EVENTS 500
#define GUILD_MAX_LOCKED_EVENTS 200
struct PointHistory {
int32 date;
string modified_by;
string comment;
float points;
bool saved_needed;
};
struct GuildMember {
int32 character_id;
int32 account_id;
int32 recruiter_id; //00 00 00 00 if not a guild recruiter
char name[64];
int32 guild_status;
float points;
int8 adventure_class;
int8 adventure_level;
int8 tradeskill_class;
int8 tradeskill_level;
int8 rank;
int8 member_flags;
string zone;
int32 join_date;
int32 last_login_date;
string note;
string officer_note;
string recruiter_description;
unsigned char* recruiter_picture_data;
int16 recruiter_picture_data_size;
int8 recruiting_show_adventure_class;
deque<PointHistory*> point_history;
};
struct GuildEvent {
int64 event_id;
int32 date;
int32 type;
string description;
int8 locked;
bool save_needed;
};
struct GuildBankEvent {
int64 event_id;
int32 date;
int32 type;
string description;
};
struct Bank {
string name;
deque<GuildBankEvent*> events;
};
class Guild {
public:
Guild();
virtual ~Guild();
void SetID(int32 id_in) {id = id_in;}
void SetName(const char* name, bool send_packet = true);
void SetLevel(int8 level, bool send_packet = true);
void SetFormedDate(int32 formed_date_in) {formed_date = formed_date_in;}
void SetMOTD(const char *motd, bool send_packet = true);
int32 GetID() const {return id;}
const char* GetName() const {return name;}
int8 GetLevel() const {return level;}
int32 GetFormedDate() const {return formed_date;}
const char * GetMOTD() const {return motd;}
void SetEXPCurrent(int64 exp, bool send_packet = true);
void AddEXPCurrent(sint64 exp, bool send_packet = true);
int64 GetEXPCurrent() const {return exp_current;}
void SetEXPToNextLevel(int64 exp, bool send_packet = true);
int64 GetEXPToNextLevel() const {return exp_to_next_level;}
void SetRecruitingShortDesc(const char* new_desc, bool send_packet = true);
string GetRecruitingShortDesc() const {return recruiting_short_desc;}
void SetRecruitingFullDesc(const char* new_desc, bool send_packet = true);
string GetRecruitingFullDesc() const {return recruiting_full_desc;}
void SetRecruitingMinLevel(int8 new_level, bool send_packet = true);
int8 GetRecruitingMinLevel() const {return recruiting_min_level;}
void SetRecruitingPlayStyle(int8 new_play_style, bool send_packet = true);
int8 GetRecruitingPlayStyle() const {return recruiting_play_style;}
bool SetRecruitingDescTag(int8 index, int8 tag, bool send_packet = true);
int8 GetRecruitingDescTag(int8 index);
bool SetPermission(int8 rank, int8 permission, int8 value, bool send_packet = true, bool save_needed = true);
int8 GetPermission(int8 rank, int8 permission);
bool SetEventFilter(int8 event_id, int8 category, int8 value, bool send_packet = true, bool save_needed = true);
int8 GetEventFilter(int8 event_id, int8 category);
int32 GetNumUniqueAccounts();
int32 GetNumRecruiters();
int32 GetNextRecruiterID();
int64 GetNextEventID();
GuildMember* GetGuildMemberOnline(Client* client);
GuildMember* GetGuildMember(Player* player);
GuildMember* GetGuildMember(int32 character_id);
GuildMember* GetGuildMember(const char* player_name);
vector<GuildMember*>* GetGuildRecruiters();
GuildEvent* GetGuildEvent(int64 event_id);
bool SetRankName(int8 rank, const char* name, bool send_packet = true);
const char* GetRankName(int8 rank);
bool SetRecruitingFlag(int8 flag, int8 value, bool send_packet = true);
int8 GetRecruitingFlag(int8 flag);
bool SetGuildRecruiter(Client* client, const char* name, bool value, bool send_packet = true);
bool SetGuildRecruiterDescription(Client* client, const char* description, bool send_packet = true);
bool ToggleGuildRecruiterAdventureClass(Client* client, bool send_packet = true);
bool SetGuildMemberNote(const char* name, const char* note, bool send_packet = true);
bool SetGuildOfficerNote(const char* name, const char* note, bool send_packet = true);
bool AddNewGuildMember(Client* client, const char* invited_by = 0, int8 rank = GUILD_RANK_RECRUIT);
bool AddNewGuildMember(int32 characterID, const char *invited_by, int32 join_timestamp, int8 rank);
bool AddGuildMember(GuildMember* guild_member);
void RemoveGuildMember(int32 character_id, bool send_packet = true);
void RemoveAllGuildMembers();
bool DemoteGuildMember(Client* client, const char* name, bool send_packet = true);
bool PromoteGuildMember(Client* client, const char* name, bool send_packet = true);
int32 KickGuildMember(Client* client, const char* name, bool send_packet = true);
bool InvitePlayer(Client* client, const char* name, bool send_packet = true);
bool AddPointsToAll(Client* client, float points, const char* comment = 0, bool send_packet = true);
bool AddPointsToAllOnline(Client* client, float points, const char* comment = 0, bool send_packet = true);
bool AddPointsToGroup(Client* client, float points, const char* comment = 0, bool send_packet = true);
bool AddPointsToRaid(Client* client, float points, const char* comment = 0, bool send_packet = true);
bool AddPointsToGuildMember(Client* client, float points, const char* name, const char* comment = 0, bool send_packet = true);
bool AddPointHistory(GuildMember* guild_member, int32 date, const char* modified_by, float points, const char* comment = 0, bool new_point_history = true);
void ViewGuildMemberPoints(Client* client, const char* name);
bool ChangeMemberFlag(Client* client, int8 member_flag, int8 value, bool send_packet = true);
bool UpdateGuildMemberInfo(Player* player);
bool UpdateGuildStatus(Player *player, int32 Status);
void AddGuildEvent(int64 event_id, int32 type, const char* description, int32 date, int8 locked);
void AddNewGuildEvent(int32 type, const char* description, int32 date, bool send_packet = true, ...);
bool LockGuildEvent(int64 event_id, bool lock, bool send_packet = true);
bool DeleteGuildEvent(int64 event_id, bool send_packet = true);
void SendGuildMOTD(Client* client);
void SendGuildEventList();
void SendGuildEventList(Client* client);
void SendGuildEventDetails();
void SendGuildEventDetails(Client* client);
void SendAllGuildEvents();
void SendAllGuildEvents(Client* client);
void SendOldGuildEvent(Client* client, GuildEvent* guild_event);
void SendNewGuildEvent(GuildEvent* guild_event);
void SendNewGuildEvent(Client* client, GuildEvent* guild_event);
void SendGuildEventAction(int8 action, GuildEvent* guild_event);
void SendGuildEventAction(Client* client, int8 action, GuildEvent* guild_event);
void SendGuildBankEventList();
void SendGuildBankEventList(Client* client);
void SendGuildUpdate();
void SendGuildUpdate(Client* client);
void SendGuildMemberList();
void SendGuildMemberList(Client* client);
void SendGuildMember(Player* player, bool include_zone = true);
void SendGuildMember(GuildMember* gm, bool include_zone = true);
void SendGuildMember(Client* client, GuildMember* gm, bool include_zone = true);
void SendGuildModification(float points, vector<int32>* character_ids);
void SendGuildModification(Client* client, float points, vector<int32>* character_ids);
void GuildMemberLogin(Client *client, bool first_login = false);
void GuildMemberLogoff(Player *player);
void SendGuildMemberLeave(int32 character_id);
void SendGuildMemberLeave(Client* client, int32 character_id);
void SendGuildRecruitingDetails(Client* client);
void SendGuildRecruitingImages(Client* client);
void SendGuildRecruiterInfo(Client* client, Player* player);
bool HandleGuildSay(Client* sender, const char* message);
void HandleGuildSay(std::string senderName, const char* message, int8 language);
bool HandleOfficerSay(Client* sender, const char* message);
void HandleOfficerSay(std::string senderName, const char* message, int8 language);
void SendMessageToGuild(int8 event_type, const char* message, ...);
void SendGuildChatMessage(const char* message, ...);
void SetSaveNeeded(bool val) {save_needed = val;}
bool GetSaveNeeded() {return save_needed;}
void SetMemberSaveNeeded(bool val) {member_save_needed = val;}
bool GetMemberSaveNeeded() {return member_save_needed;}
void SetEventsSaveNeeded(bool val) {events_save_needed = val;}
bool GetEventsSaveNeeded() {return events_save_needed;}
void SetRanksSaveNeeded(bool val) {ranks_save_needed = val;}
bool GetRanksSaveNeeded() {return ranks_save_needed;}
void SetEventFiltersSaveNeeded(bool val) {event_filters_save_needed = val;}
bool GetEventFiltersSaveNeeded() {return event_filters_save_needed;}
void SetPointsHistorySaveNeeded(bool val) {points_history_save_needed = val;}
bool GetPointsHistorySaveNeeded() {return points_history_save_needed;}
void SetRecruitingSaveNeeded(bool val) {recruiting_save_needed = val;}
bool GetRecruitingSaveNeeded() {return recruiting_save_needed;}
map<int32, GuildMember*>* GetGuildMembers() {return &members;}
Mutex * GetGuildMembersMutex() {return &mMembers;}
deque<GuildEvent*>* GetGuildEvents() {return &guild_events;}
MutexMap<int8, MutexMap<int8, int8>*>* GetPermissions() {return &permissions;}
MutexMap<int8, string>* GetGuildRanks() {return &ranks;}
MutexMap<int8, int8>* GetRecruitingFlags() {return &recruiting_flags;}
MutexMap<int8, int8>* GetRecruitingDescTags() {return &recruiting_desc_tags;}
int8 GetRecruitingLookingForPacketValue();
static string GetEpicMobDeathMessage(const char* player_name, const char* mob_name);
private:
int32 id;
char name[64];
int8 level;
int32 formed_date;
char motd[256];
int64 exp_current;
int64 exp_to_next_level;
string recruiting_short_desc;
string recruiting_full_desc;
int8 recruiting_min_level;
int8 recruiting_play_style;
MutexMap<int8, string> ranks;
map<int32, GuildMember*> members;
Mutex mMembers;
deque<GuildEvent*> guild_events;
MutexMap<int8, MutexMap<int8, int8>*> permissions;
MutexMap<int8, MutexMap<int8, int8>*> event_filters;
MutexMap<int8, int8> recruiting_flags;
MutexMap<int8, int8> recruiting_desc_tags;
Bank banks[4];
int32 GetPermissionsPacketValue(int8 rank, int32 start, int32 end);
int32 GetEventFilterPacketValue(int8 category, int32 start, int32 end);
bool save_needed;
bool member_save_needed;
bool events_save_needed;
bool event_filters_save_needed;
bool ranks_save_needed;
bool points_history_save_needed;
bool recruiting_save_needed;
};
class GuildList {
public:
GuildList();
virtual ~GuildList();
bool AddGuild(Guild* guild);
Guild* GetGuild(int32 guild_id);
Guild* GetGuild(const char* guild_name);
bool RemoveGuild(Guild* guild, bool delete_data = false);
bool RemoveGuild(int32 guild_id, bool delete_data = false);
int32 GetNumGuilds() {return guild_list.size();}
MutexMap<int32, Guild*>* GetGuilds() {return &guild_list;}
private:
MutexMap<int32, Guild*> guild_list;
};
#endif

View File

@ -0,0 +1,615 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifdef WIN32
#include <WinSock2.h>
#include <windows.h>
#endif
#include <math.h>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <ios>
#include <mysql.h>
#include <assert.h>
#include "../../common/Log.h"
#include "../Worlddatabase.hpp"
#include "Guild.h"
extern GuildList guild_list;
extern RuleManager rule_manager;
void WorldDatabase::LoadGuilds() {
int32 num_guilds = 0;
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `name`, `motd`, `level`, `xp`, `xp_needed`, `formed_on` FROM `guilds`");
while (result && (row = mysql_fetch_row(result))) {
LogWrite(GUILD__DEBUG, 1, "Guilds", "%u. %s", atoul(row[0]), row[1]);
Guild* guild = new Guild;
guild->SetID(atoul(row[0]));
guild->SetName(row[1]);
if (row[2])
guild->SetMOTD(row[2], false);
guild->SetLevel(atoi(row[3]), false);
guild->SetEXPCurrent(atoul(row[4]), false);
guild->SetEXPToNextLevel(atoul(row[5]), false);
guild->SetFormedDate(atoul(row[6]));
LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoaded %i guild members.", LoadGuildMembers(guild));
LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Ranks...");
LoadGuildRanks(guild);
LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Event Filters...");
LoadGuildEventFilters(guild);
LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Events...");
LoadGuildEvents(guild);
LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Recruiting...");
LoadGuildRecruiting(guild);
guild_list.AddGuild(guild);
num_guilds++;
}
LogWrite(GUILD__INFO, 0, "Guilds", "\tLoaded %u Guild(s)", num_guilds);
}
void WorldDatabase::LoadGuild(int32 guild_id) {
Query query;
MYSQL_ROW row;
Guild* tmpGuild = guild_list.GetGuild(guild_id);
if(tmpGuild) // already loaded
return;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `name`, `motd`, `level`, `xp`, `xp_needed`, `formed_on` FROM `guilds` where id=%u", guild_id);
if (result && (row = mysql_fetch_row(result))) {
LogWrite(GUILD__DEBUG, 1, "Guilds", "%u. %s", atoul(row[0]), row[1]);
Guild* guild = new Guild;
guild->SetID(atoul(row[0]));
guild->SetName(row[1]);
if (row[2])
guild->SetMOTD(row[2], false);
guild->SetLevel(atoi(row[3]), false);
guild->SetEXPCurrent(atoul(row[4]), false);
guild->SetEXPToNextLevel(atoul(row[5]), false);
guild->SetFormedDate(atoul(row[6]));
LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoaded %i guild members.", LoadGuildMembers(guild));
LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Ranks...");
LoadGuildRanks(guild);
LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Event Filters...");
LoadGuildEventFilters(guild);
LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Events...");
LoadGuildEvents(guild);
LogWrite(GUILD__DEBUG, 3, "Guilds", "\tLoading Guild Recruiting...");
LoadGuildRecruiting(guild);
guild_list.AddGuild(guild);
}
}
int32 WorldDatabase::LoadGuildMembers(Guild* guild) {
int32 num_members = 0;
Query query;
MYSQL_ROW row;
char *name;
int32 char_id;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `char_id`, `recruiter_id`, `guild_status`, `points`, `rank_id`, `member_flags`, `join_date`, `note`, `officer_note`, `recruiting_message`, `recruiter_picture_data` FROM `guild_members` WHERE `guild_id`=%u", guild->GetID());
while (result && (row = mysql_fetch_row(result))) {
char_id = atoul(row[0]);
if (!(name = GetCharacterName(char_id))) {
LogWrite(GUILD__ERROR, 0, "Guilds", "WorldDatabase::LoadGuildMembers Cannot find guild member with character id %u.", char_id);
continue;
}
GuildMember* gm = new GuildMember;
gm->character_id = char_id;
gm->recruiter_id = atoul(row[1]);
gm->guild_status = atoul(row[2]);
gm->points = atof(row[3]);
gm->rank = atoi(row[4]);
gm->member_flags = atoi(row[5]);
gm->join_date = atoul(row[6]);
if (row[7])
gm->note = string(row[7]);
if (row[8])
gm->officer_note = string(row[8]);
if (row[9])
gm->recruiter_description = string(row[9]);
int16 recruiter_picture_data_size = 0;
if (row[10] && (recruiter_picture_data_size = strlen(row[10])) > 0) {
gm->recruiter_picture_data_size = recruiter_picture_data_size / 2;
gm->recruiter_picture_data = new unsigned char[gm->recruiter_picture_data_size];
unsigned char* cpy = gm->recruiter_picture_data;
const char* str = row[10];
char high, low;
for (const char* ptr = str; *ptr; ptr += 2) {
high = tolower(*ptr);
low = tolower(*(ptr+1));
if (isdigit(high))
high = high - '0';
else if (high >= 'a' && high <= 'f')
high = (high - 'a') + 10;
else {
LogWrite(GUILD__ERROR, 0, "Guilds", "Guild mate with id %u has corrupt picture data. Data not loading.", gm->character_id);
safe_delete_array(gm->recruiter_picture_data);
gm->recruiter_picture_data_size = 0;
break;
}
if (isdigit(low))
low = low - '0';
else if (low >= 'a' && low <= 'f')
low = (low - 'a') + 10;
else {
LogWrite(GUILD__ERROR, 0, "Guilds", "Guild mate with id %u has corrupt picture data. Data not loading.", gm->character_id);
safe_delete_array(gm->recruiter_picture_data);
gm->recruiter_picture_data_size = 0;
break;
}
*cpy++ = low | (high << 4);
}
/*for (int16 i = 0; i < gm->recruiter_picture_data_size; i++)
if (i<10)
printf("int:%u hex:%x\n", gm->recruiter_picture_data[i], gm->recruiter_picture_data[i]);*/
}
else {
gm->recruiter_picture_data_size = 0;
gm->recruiter_picture_data = 0;
}
strncpy(gm->name, name, sizeof(gm->name));
gm->account_id = GetCharacterAccountID(char_id);
gm->adventure_class = GetCharacterClass(char_id);
gm->adventure_level = GetCharacterLevel(char_id);
gm->tradeskill_class = 0;
gm->tradeskill_level = 0;
gm->last_login_date = GetCharacterTimeStamp(char_id);
gm->zone = GetZoneDescription(GetCharacterCurrentZoneID(char_id));
gm->recruiting_show_adventure_class = 1;
LoadGuildPointsHistory(guild, gm);
guild->AddGuildMember(gm);
safe_delete_array(name);
num_members++;
}
return num_members;
}
void WorldDatabase::LoadGuildEvents(Guild* guild) {
if (guild) {
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `event_id`, `event_date`, `event_type`, `description`, `locked` FROM `guild_events` WHERE `guild_id`=%u AND `display`=1 AND `archived`=0 ORDER BY `event_date` DESC LIMIT 0, %u", guild->GetID(), GUILD_MAX_EVENTS);
while (result && (row = mysql_fetch_row(result)))
guild->AddGuildEvent(atoi64(row[0]), atoul(row[2]), row[3], atoul(row[1]), atoi(row[4]));
}
}
void WorldDatabase::LoadGuildRanks(Guild* guild) {
if (guild) {
LogWrite(GUILD__DEBUG, 3, "Guilds", "Loading Ranks for guild id: %u", guild->GetID());
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `rank_id`, `rank_name`, `permission1`, `permission2` FROM `guild_ranks` WHERE `guild_id`=%u", guild->GetID());
while (result && (row = mysql_fetch_row(result))) {
int8 rank_id = atoi(row[0]);
int32 permission1 = atoul(row[2]);
int32 permission2 = atoul(row[3]);
guild->SetRankName(rank_id, row[1], false);
LogWrite(GUILD__DEBUG, 5, "Guilds", "\tLoading rank_id: %i", rank_id);
LogWrite(GUILD__DEBUG, 5, "Guilds", "\tPermission1: %ul, Permission2: %ul", permission1, permission2);
for (int32 i = 0; i <= 44; i++) {
int32 bitwise_val;
if (i < 32) {
bitwise_val = (int32)pow(2.0, (double)(i));
guild->SetPermission(rank_id, i, permission1 & bitwise_val ? 1 : 0, false);
LogWrite(GUILD__DEBUG, 5, "Guilds", "\tSetting Permission %u to %u", i, permission1 & bitwise_val ? 1 : 0);
}
else {
bitwise_val = (int32)pow(2.0, (double)(i - 32));
guild->SetPermission(rank_id, i, permission2 & bitwise_val ? 1 : 0, false);
LogWrite(GUILD__DEBUG, 5, "Guilds", "\tSetting Permission %u to %u", i, permission2 & bitwise_val ? 1 : 0);
}
}
}
}
}
void WorldDatabase::LoadGuildEventFilters(Guild* guild) {
if (guild) {
Query query;
MYSQL_ROW row;
bool event_filter_added = false;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `event_id`, `retain`, `broadcast` FROM `guild_event_filters` WHERE `guild_id`=%u", guild->GetID());
while (result && (row = mysql_fetch_row(result))) {
guild->SetEventFilter(atoi(row[0]), GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY, atoi(row[1]), false);
guild->SetEventFilter(atoi(row[0]), GUILD_EVENT_FILTER_CATEGORY_BROADCAST, atoi(row[2]), false);
if (!event_filter_added)
event_filter_added = true;
}
if (!event_filter_added)
LoadGuildDefaultEventFilters(guild);
}
}
void WorldDatabase::LoadGuildPointsHistory(Guild* guild, GuildMember* guild_member) {
Query query;
MYSQL_ROW row;
MYSQL_RES* result;
assert(guild);
assert(guild_member);
result = query.RunQuery2(Q_SELECT, "SELECT `points_date`, `modified_by`, `comment`, `points` FROM `guild_points_history` WHERE `guild_id`=%u AND `char_id`=%u ORDER BY `points_date` DESC", guild->GetID(), guild_member->character_id);
while (result && (row = mysql_fetch_row(result)))
guild->AddPointHistory(guild_member, atoul(row[0]), row[1], atof(row[3]), row[2], false);
}
void WorldDatabase::LoadGuildRecruiting(Guild* guild) {
if (guild) {
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `short_desc`, `full_desc`, `min_level`, `play_style`, `looking_for`, `descriptive_tag1`, `descriptive_tag2`, `descriptive_tag3`, `descriptive_tag4` FROM `guild_recruiting` WHERE `guild_id`=%u", guild->GetID());
while (result && (row = mysql_fetch_row(result))) {
if (row[0])
guild->SetRecruitingShortDesc(row[0], false);
if (row[1])
guild->SetRecruitingFullDesc(row[1], false);
guild->SetRecruitingMinLevel(atoi(row[2]), false);
guild->SetRecruitingPlayStyle(atoi(row[3]), false);
for (int32 i = 0; i <= 5; i++) {
int32 bitwise_val = (int32)pow(2.0, (double)i);
guild->SetRecruitingFlag(i, atoi(row[4]) & bitwise_val ? 1 : 0, false);
}
guild->SetRecruitingDescTag(0, atoi(row[5]), false);
guild->SetRecruitingDescTag(1, atoi(row[6]), false);
guild->SetRecruitingDescTag(2, atoi(row[7]), false);
guild->SetRecruitingDescTag(3, atoi(row[8]), false);
}
}
}
void WorldDatabase::SaveGuild(Guild* guild, bool new_guild) {
Query query;
assert(guild);
if (new_guild) {
LogWrite(GUILD__DEBUG, 3, "Guilds", "Saving NEW Guild '%s' (%u) data...", guild->GetName(), guild->GetID());
query.RunQuery2(Q_INSERT, "INSERT INTO `guilds` (`name`, `motd`, `level`, `xp`, `xp_needed`, `formed_on`) "
"VALUES ('%s', '%s', %i, %llu, %llu, %u)",
getSafeEscapeString(guild->GetName()).c_str(), getSafeEscapeString(guild->GetMOTD()).c_str(), guild->GetLevel(), guild->GetEXPCurrent(), guild->GetEXPToNextLevel(), guild->GetFormedDate());
guild->SetID(query.GetLastInsertedID());
}
else {
LogWrite(GUILD__DEBUG, 3, "Guilds", "Saving Guild '%s' (%u) data...", guild->GetName(), guild->GetID());
query.RunQuery2(Q_UPDATE, "UPDATE `guilds` "
"SET `name`='%s', `motd`='%s', `level`=%i, `xp`=%llu, `xp_needed`=%llu, `formed_on`=%u WHERE `id`=%u",
getSafeEscapeString(guild->GetName()).c_str(), getSafeEscapeString(guild->GetMOTD()).c_str(), guild->GetLevel(), guild->GetEXPCurrent(), guild->GetEXPToNextLevel(), guild->GetFormedDate(), guild->GetID());
}
guild->SetSaveNeeded(false);
}
void WorldDatabase::SaveGuildMembers(Guild* guild) {
map<int32, GuildMember *>* members;
map<int32, GuildMember *>::iterator itr;
Mutex *mMembers;
GuildMember *gm;
Query query, query2;
assert(guild);
members = guild->GetGuildMembers();
mMembers = guild->GetGuildMembersMutex();
mMembers->readlock(__FUNCTION__, __LINE__);
for (itr = members->begin(); itr != members->end(); itr++) {
gm = itr->second;
LogWrite(GUILD__DEBUG, 5, "Guilds", "Saving Guild Member '%s' (%u) data...", gm->name, gm->character_id);
query.RunQuery2(Q_INSERT, "INSERT INTO `guild_members` (`guild_id`, `char_id`, `recruiter_id`, `guild_status`, `points`, `rank_id`, `member_flags`, `join_date`, `note`, `officer_note`, `recruiting_message`, `recruiter_picture_data`) VALUES (%u, %u, %u, %u, %f, %u, %u, %u, '%s', '%s', '%s', NULL) ON DUPLICATE KEY UPDATE `guild_id`=%u, `recruiter_id`=%u, `guild_status`=%u, `points`=%f, `rank_id`=%u, `member_flags`=%u, `join_date`=%u, `note`='%s', `officer_note`='%s', `recruiting_message`='%s', `recruiter_picture_data`=NULL", guild->GetID(), gm->character_id, gm->recruiter_id, gm->guild_status, gm->points, gm->rank, gm->member_flags, gm->join_date, getSafeEscapeString(gm->note.c_str()).c_str(), getSafeEscapeString(gm->officer_note.c_str()).c_str(), getSafeEscapeString(gm->recruiter_description.c_str()).c_str(), guild->GetID(), gm->recruiter_id, gm->guild_status, gm->points, gm->rank, gm->member_flags, gm->join_date, getSafeEscapeString(gm->note.c_str()).c_str(), getSafeEscapeString(gm->officer_note.c_str()).c_str(), getSafeEscapeString(gm->recruiter_description.c_str()).c_str());
if (gm && gm->recruiter_picture_data_size > 0 && gm->recruiter_picture_data) {
stringstream ss_hex;
stringstream ss_query;
ss_hex.flags(ios::hex);
for (int16 i = 0; i < gm->recruiter_picture_data_size; i++)
ss_hex << setfill('0') << setw(2) << (int)gm->recruiter_picture_data[i];
ss_query << "UPDATE `guild_members` SET `recruiter_picture_data`='" << ss_hex.str() << "' WHERE `char_id`=" << gm->character_id;
query2.RunQuery2(ss_query.str(), Q_UPDATE);
}
}
guild->SetMemberSaveNeeded(false);
mMembers->releasereadlock(__FUNCTION__, __LINE__);
}
void WorldDatabase::SaveGuildEvents(Guild* guild) {
if (guild) {
deque<GuildEvent*>* guild_events = guild->GetGuildEvents();
deque<GuildEvent*>::iterator itr;
for (itr = guild_events->begin(); itr != guild_events->end(); itr++) {
GuildEvent* ge = *itr;
if (!ge->save_needed)
continue;
LogWrite(GUILD__DEBUG, 5, "Guilds", "Saving Guild Events for guild '%s' (%u)...", guild->GetName(), guild->GetID());
Query query;
query.RunQuery2(Q_INSERT, "INSERT INTO `guild_events` (`guild_id`, `event_id`, `event_date`, `event_type`, `description`, `display`, `locked`, `archived`) "
"VALUES (%u, %llu, %u, %u, '%s', 1, %u, 0) "
"ON DUPLICATE KEY UPDATE `locked`=%i",
guild->GetID(), ge->event_id, ge->date, ge->type, getSafeEscapeString(ge->description.c_str()).c_str(), ge->locked, ge->locked);
ge->save_needed = false;
}
guild->SetEventsSaveNeeded(false);
}
}
void WorldDatabase::SaveGuildRanks(Guild* guild) {
if (guild) {
MutexMap<int8, MutexMap<int8, int8>*>* permissions = guild->GetPermissions();
MutexMap<int8, string>* ranks = guild->GetGuildRanks();
MutexMap<int8, string>::iterator ranks_itr = ranks->begin();
while (ranks_itr.Next()) {
int32 permission1 = 0;
int32 permission2 = 0;
for (int32 i = 0; i <= 44; i++) {
if (permissions->count(ranks_itr.first) > 0 && permissions->Get(ranks_itr.first)->count(i) > 0 && permissions->Get(ranks_itr.first)->Get(i)) {
if (i < 32)
permission1 += (int32)pow(2.0, (double)i);
else
permission2 += (int32)pow(2.0, (double)(i - 32));
}
}
LogWrite(GUILD__DEBUG, 5, "Guilds", "Saving Guild Ranks for guild '%s' (%u)...", guild->GetName(), guild->GetID());
Query query;
query.RunQuery2(Q_INSERT, "INSERT INTO `guild_ranks` (`guild_id`, `rank_id`, `rank_name`, `permission1`, `permission2`) "
"VALUES (%u, %u, '%s', %u, %u) "
"ON DUPLICATE KEY UPDATE `rank_name`='%s', `permission1`=%u, permission2=%u",
guild->GetID(), ranks_itr.first, getSafeEscapeString(ranks_itr.second.c_str()).c_str(), permission1, permission2, getSafeEscapeString(ranks_itr.second.c_str()).c_str(), permission1, permission2);
}
guild->SetRanksSaveNeeded(false);
}
}
void WorldDatabase::SaveGuildEventFilters(Guild* guild) {
int32 i;
assert(guild);
for (i = 0; i < 93; i++) {
LogWrite(GUILD__DEBUG, 5, "Guilds", "Saving Guild EventFilters for guild '%s' (%u)...", guild->GetName(), guild->GetID());
Query query;
query.RunQuery2(Q_INSERT, "INSERT INTO `guild_event_filters` (`guild_id`, `event_id`, `retain`, `broadcast`) "
"VALUES (%u, %u, %u, %u) "
"ON DUPLICATE KEY UPDATE `retain`=%u, `broadcast`=%u",
guild->GetID(), i, guild->GetEventFilter(i, GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY), guild->GetEventFilter(i, GUILD_EVENT_FILTER_CATEGORY_BROADCAST), guild->GetEventFilter(i, GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY), guild->GetEventFilter(i, GUILD_EVENT_FILTER_CATEGORY_BROADCAST));
}
guild->SetEventFiltersSaveNeeded(false);
}
void WorldDatabase::SaveGuildPointsHistory(Guild* guild) {
map<int32, GuildMember *> *members;
map<int32, GuildMember *>::iterator itr;
Mutex *mMembers;
deque<PointHistory *> *ph_list;
deque<PointHistory*>::iterator ph_itr;
PointHistory* ph;
assert (guild);
members = guild->GetGuildMembers();
mMembers = guild->GetGuildMembersMutex();
mMembers->readlock(__FUNCTION__, __LINE__);
for (itr = members->begin(); itr != members->end(); itr++) {
ph_list = &itr->second->point_history;
for (ph_itr = ph_list->begin(); ph_itr != ph_list->end(); ph_itr++) {
ph = *ph_itr;
if (!ph->saved_needed)
continue;
LogWrite(GUILD__DEBUG, 5, "Guilds", "Saving Guild Point History for guild '%s' (%u)...", guild->GetName(), guild->GetID());
Query query;
query.RunQuery2(Q_INSERT, "INSERT INTO `guild_points_history` (`guild_id`, `char_id`, `points_date`, `modified_by`, `comment`, `points`) "
"VALUES (%u, %u, %u, '%s', '%s', %f)",
guild->GetID(), itr->first, ph->date, getSafeEscapeString(ph->modified_by.c_str()).c_str(), getSafeEscapeString(ph->comment.c_str()).c_str(), ph->points);
ph->saved_needed = false;
}
}
guild->SetPointsHistorySaveNeeded(false);
mMembers->releasereadlock(__FUNCTION__, __LINE__);
}
void WorldDatabase::SaveGuildRecruiting(Guild* guild) {
if (guild) {
LogWrite(GUILD__DEBUG, 3, "Guilds", "Saving Recruiting info for guild '%s' (%u)...", guild->GetName(), guild->GetID());
Query query;
query.RunQuery2(Q_INSERT, "INSERT INTO `guild_recruiting` (`guild_id`, `short_desc`, `full_desc`, `min_level`, `play_style`, `looking_for`, `descriptive_tag1`, `descriptive_tag2`, `descriptive_tag3`, `descriptive_tag4`) VALUES (%u, '%s', '%s', %u, %u, %u, %u, %u, %u, %u) ON DUPLICATE KEY UPDATE `short_desc`='%s', `full_desc`='%s', `min_level`=%u, `play_style`=%u, `looking_for`=%u, `descriptive_tag1`=%u, `descriptive_tag2`=%u, `descriptive_tag3`=%u, `descriptive_tag4`=%u", guild->GetID(), getSafeEscapeString(guild->GetRecruitingShortDesc().c_str()).c_str(), getSafeEscapeString(guild->GetRecruitingFullDesc().c_str()).c_str(), guild->GetRecruitingMinLevel(), guild->GetRecruitingPlayStyle(), guild->GetRecruitingLookingForPacketValue(), guild->GetRecruitingDescTag(0), guild->GetRecruitingDescTag(1), guild->GetRecruitingDescTag(2), guild->GetRecruitingDescTag(3), getSafeEscapeString(guild->GetRecruitingShortDesc().c_str()).c_str(), getSafeEscapeString(guild->GetRecruitingFullDesc().c_str()).c_str(), guild->GetRecruitingMinLevel(), guild->GetRecruitingPlayStyle(), guild->GetRecruitingLookingForPacketValue(), guild->GetRecruitingDescTag(0), guild->GetRecruitingDescTag(1), guild->GetRecruitingDescTag(2), guild->GetRecruitingDescTag(3));
guild->SetRecruitingSaveNeeded(false);
}
}
void WorldDatabase::DeleteGuild(Guild* guild) {
if (guild) {
LogWrite(GUILD__DEBUG, 3, "Guilds", "Deleting Guild '%s' (%u)...", guild->GetName(), guild->GetID());
Query query;
query.RunQuery2(Q_DELETE, "DELETE FROM `guilds` WHERE `id`=%u", guild->GetID());
}
}
void WorldDatabase::DeleteGuildMember(Guild* guild, int32 character_id) {
if (guild) {
LogWrite(GUILD__DEBUG, 3, "Guilds", "Deleting Character (%u) from guild '%s' (%u)...", character_id, guild->GetName(), guild->GetID());
Query query;
query.RunQuery2(Q_DELETE, "DELETE FROM `guild_members` WHERE `guild_id`=%u AND `char_id`=%u", guild->GetID(), character_id);
}
}
void WorldDatabase::DeleteGuildEvent(Guild* guild, int64 event_id) {
if (guild) {
LogWrite(GUILD__DEBUG, 3, "Guilds", "Deleting Event (%u) from guild '%s' (%u)...", event_id, guild->GetName(), guild->GetID());
Query query;
query.RunQuery2(Q_DELETE, "DELETE FROM `guild_events` WHERE `guild_id`=%u AND `event_id`=%u", guild->GetID(), event_id);
}
}
void WorldDatabase::DeleteGuildPointHistory(Guild* guild, int32 character_id, PointHistory* point_history) {
if (guild && point_history) {
LogWrite(GUILD__DEBUG, 3, "Guilds", "Deleting PointHistory for Character (%u) from guild '%s' (%u)...", character_id, guild->GetName(), guild->GetID());
Query query;
query.RunQuery2(Q_DELETE, "DELETE FROM `guild_points_history` WHERE `guild_id`=%u AND `char_id`=%u AND `points_date`=%u", guild->GetID(), character_id, point_history->date);
}
}
void WorldDatabase::ArchiveGuildEvent(Guild* guild, GuildEvent* guild_event) {
if (guild && guild_event) {
LogWrite(GUILD__DEBUG, 3, "Guilds", "Archiving Event (%u) for guild '%s' (%u)...", guild_event->event_id, guild->GetName(), guild->GetID());
Query query;
query.RunQuery2(Q_UPDATE, "UPDATE `guild_events` SET `archived`=1 WHERE `guild_id`=%u AND `event_id`=%u", guild->GetID(), guild_event->event_id);
}
}
void WorldDatabase::SaveHiddenGuildEvent(Guild* guild, GuildEvent* guild_event) {
if (guild && guild_event) {
LogWrite(GUILD__DEBUG, 3, "Guilds", "Saving Hidden Event (%u) for guild '%s' (%u)...", guild_event->event_id, guild->GetName(), guild->GetID());
Query query;
query.RunQuery2(Q_INSERT, "INSERT INTO `guild_events` (`guild_id`, `event_id`, `event_date`, `event_type`, `description`, `display`, `locked`, `archived`) VALUES (%u, %u, %u, %u, '%s', 0, %u, 0)", guild->GetID(), guild_event->event_id, guild_event->type, guild_event->date, getSafeEscapeString(guild_event->description.c_str()).c_str(), guild_event->locked);
}
}
int32 WorldDatabase::GetGuildIDByCharacterID(int32 char_id) {
if(char_id > 0)
{
LogWrite(GUILD__DEBUG, 3, "Guilds", "Look up guild ID for player ID: '%u'...", char_id);
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT name FROM guilds, guild_members WHERE guilds.id = guild_members.guild_id AND char_id = %u ", char_id);
while (result && (row = mysql_fetch_row(result))) {
if( row[0] )
return atoul(row[0]);
}
}
return 0;
}
void WorldDatabase::LoadGuildDefaultRanks(Guild* guild) {
if (guild) {
LogWrite(GUILD__DEBUG, 3, "Guilds", "Load/Set Default Ranks for guild '%s' (%u)...", guild->GetName(), guild->GetID());
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT DISTINCT `rank_id`, `rank_name`, `permission1`, `permission2` FROM `guild_ranks_defaults`");
while (result && (row = mysql_fetch_row(result))) {
int8 rank_id = atoi(row[0]);
int32 permission1 = atoul(row[2]);
int32 permission2 = atoul(row[3]);
LogWrite(GUILD__DEBUG, 3, "Guilds", "\tSetting RankID %i, permission1: %u, permission2: %u", rank_id, permission1, permission2);
guild->SetRankName(rank_id, row[1], false);
for (int32 i = 0; i <= 44; i++) {
int32 bitwise_val;
if (i < 32) {
bitwise_val = (int32)pow(2.0, (double)i);
guild->SetPermission(rank_id, i, permission1 & bitwise_val ? 1 : 0, false);
}
else {
bitwise_val = (int32)pow(2.0, (double)(i - 32));
guild->SetPermission(rank_id, i, permission2 & bitwise_val ? 1 : 0, false);
}
}
}
}
}
void WorldDatabase::LoadGuildDefaultEventFilters(Guild* guild) {
if (guild) {
LogWrite(GUILD__DEBUG, 3, "Guilds", "Load/Set Default Event Filters for guild '%s' (%u)...", guild->GetName(), guild->GetID());
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT DISTINCT `event_id`, `retain`, `broadcast` FROM `guild_event_defaults`");
while (result && (row = mysql_fetch_row(result))) {
LogWrite(GUILD__DEBUG, 3, "Guilds", "\tSetting Event Filter %i, retain: %i, broadcast: %i", atoi(row[0]), atoi(row[1]), atoi(row[2]));
guild->SetEventFilter(atoi(row[0]), GUILD_EVENT_FILTER_CATEGORY_RETAIN_HISTORY, atoi(row[1]), false);
guild->SetEventFilter(atoi(row[0]), GUILD_EVENT_FILTER_CATEGORY_BROADCAST, atoi(row[2]), false);
}
}
}
bool WorldDatabase::AddNewPlayerToServerGuild(int32 account_id, int32 char_id)
{
// Check if this servers rule allow auto-joining Server guild
int8 autojoin = rule_manager.GetGlobalRule(R_World, GuildAutoJoin)->GetInt8();
if( autojoin )
{
// if so, what is the guild ID of the default server guild?
int32 guild_id = rule_manager.GetGlobalRule(R_World, GuildAutoJoinID)->GetInt32();
Guild* guild = 0;
guild = guild_list.GetGuild(guild_id);
if (!guild)
{
// guild was not valid, abort!
LogWrite(GUILD__ERROR, 1, "Guilds", "Guild ID %u not found! Cannot autojoin members!", guild_id);
return false;
}
else
{
// guild was found, so what default Rank to make the players? if not set, use 7 (recruit)
int8 rank_id = rule_manager.GetGlobalRule(R_World, GuildAutoJoinDefaultRankID)->GetInt8();
if(!rank_id)
rank_id = 7;
// assuming all is good, insert the new guild member here...
GuildMember *gm = new GuildMember();
gm->account_id = account_id;
gm->character_id = char_id;
char* name = GetCharacterName(gm->character_id);
strncpy(gm->name, name, sizeof(gm->name));
gm->guild_status = 0;
gm->points = 0.0;
//gm->adventure_class = player->GetAdventureClass();
//gm->adventure_level = player->GetLevel();
//gm->tradeskill_class = player->GetTradeskillClass();
//gm->tradeskill_level = player->GetTSLevel();
gm->rank = rank_id;
gm->zone = string("");
gm->join_date = Timer::GetUnixTimeStamp();
gm->last_login_date = gm->join_date;
gm->recruiter_id = 0;
gm->member_flags = GUILD_MEMBER_FLAGS_NOTIFY_LOGINS;
gm->recruiting_show_adventure_class = 1;
gm->recruiter_picture_data_size = 0;
gm->recruiter_picture_data = 0;
guild->AddGuildMember(gm);
Query query;
query.RunQuery2(Q_INSERT, "INSERT INTO `guild_members` (`guild_id`, `char_id`, `join_date`, `rank_id`) VALUES (%u, %u, %u, %i)",
guild_id, char_id, gm->join_date, rank_id);
LogWrite(GUILD__DEBUG, 3, "Guilds", "Auto-join player (%u) to server guild '%s' (%u) at rank %i...", char_id, guild->GetName(), guild_id, rank_id);
// success!
return true;
}
}
// do not auto-join server guild
return false;
}

View File

@ -0,0 +1,315 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "HeroicOp.h"
#include "../../common/Log.h"
#include "../Rules/Rules.h"
extern MasterHeroicOPList master_ho_list;
extern RuleManager rule_manager;
HeroicOP::HeroicOP() {
m_complete = 0;
m_currentStage = 0;
m_wheel = 0;
m_target = 0;
m_startTime = 0;
m_totalTime = 0;
m_shifted = false;
for (int8 i = 0; i < 6; i++)
countered[i] = 0;
}
HeroicOP::~HeroicOP() {
starters.clear();
}
void HeroicOP::SetWheel(HeroicOPWheel* val) {
if (!m_wheel)
m_wheel = val;
else
LogWrite(SPELL__ERROR, 0, "HO", "Attempted to set the wheel on a heroic op with a wheel already set");
}
void HeroicOP::SetTarget(int32 val) {
m_target = val;
}
bool HeroicOP::UpdateHeroicOP(int16 icon) {
bool ret = false;
vector<HeroicOPStarter*>::iterator itr;
vector<HeroicOPStarter*> temp;
HeroicOPStarter* starter = 0;
LogWrite(SPELL__DEBUG, 0, "HO", "Current Stage %u, wheel exists: %u, looking for icon %u", m_currentStage, m_wheel ? 1 : 0, icon);
// If no wheel is set we are dealing with a starter chain still.
if (!m_wheel) {
// Loop through the starter chains
for (itr = starters.begin(); itr != starters.end(); itr++) {
starter = *itr;
// See if the icon matches the ability at our current stage, if not add it to a list to be removed
if (starter->abilities[m_currentStage] == icon)
ret = true;
else
temp.push_back(*itr);
}
if (ret) {
// ret = true so we had a match, first thing to do is remove those that didn't match
vector<HeroicOPStarter*>::iterator remove_itr;
for (remove_itr = temp.begin(); remove_itr != temp.end(); remove_itr++)
{
std::vector<HeroicOPStarter*>::iterator it = std::find(starters.begin(), starters.end(), *remove_itr);
starters.erase(it);
}
// now advance the current stage
m_currentStage++;
// Temp pointer to hold the completed chain, if any
HeroicOPStarter* complete_starter = 0;
// now loop through those that are left and check the next stage abilities for a 0xFFFF
for (itr = starters.begin(); itr != starters.end(); itr++) {
starter = *itr;
// Found one that is 0xFFFF, means the starter chain is done, get a wheel and reset the stage to 0
if ((starter->abilities[m_currentStage] == 0xFFFF)) {
LogWrite(SPELL__DEBUG, 0, "HO", "Current Stage %u, starter reset (new stage 0)", m_currentStage);
// reset the stage
ResetStage();
// geth the wheel
m_wheel = master_ho_list.GetWheel(starter);
// store the starter chain that is completed
complete_starter = starter;
// set the start time to now
SetStartTime(Timer::GetCurrentTime2());
// set the total time to complete the real to was the admin set in rules (default 10.0f)
SetTotalTime(rule_manager.GetGlobalRule(R_Zone, HOTime)->GetFloat());
// We set a wheel so we are done, kill the loop
break;
}
}
// Check to see if the completed start chain pointer was set
if (complete_starter) {
LogWrite(SPELL__DEBUG, 0, "HO", "Current Stage %u, complete_starter set", m_currentStage);
// clear the starter list
starters.clear();
// add the completed starter back in, we do this in case we need this starter again we can just do starters.at(0), for example shifting the wheel
starters.push_back(complete_starter);
}
}
}
else {
LogWrite(SPELL__DEBUG, 0, "HO", "Current Stage %u, wheel order: %u", m_currentStage, m_wheel->order);
// Wheel was set so we need to check the order it needs to be completed in.
if (m_wheel->order == 0) {
// No order
// Flag used to see if we can shift the wheel
bool can_shift = true;
// Check the icons and flag the ability as countered if there is a match
for (int8 i = 0; i < 6; i++) {
if (countered[i] == 1) {
// progress made on this wheel so we can't shift it
can_shift = false;
}
if (m_wheel->abilities[i] == icon) {
countered[i] = 1;
ret = true;
}
}
if (ret) {
// As we found a match lets loop through to see if we completed the ho
bool finished = true;
for (int8 i = 0; i < 6; i++) {
// if the ability is not 0xFFFF and countered is 0 then we still have more to go
if (m_wheel->abilities[i] != 0xFFFF && countered[i] == 0) {
finished = false;
break;
}
}
// is we finished the ho set the complete flag
if (finished)
SetComplete(2);
}
if (!ret && can_shift && m_wheel->shift_icon == icon) {
// can shift, icon matched shift icon, and no progress made
ret = ShiftWheel();
}
}
else {
// In order
// Check to see if we can shift the wheel
if (countered[0] == 0 && icon == m_wheel->shift_icon) {
// Can only shift the icon if nothing has completed yet (countered[0] = 0)
ret = ShiftWheel();
}
// Check the current stage and compare it to the icon
else if (m_wheel->abilities[m_currentStage] == icon) {
// Is a match so flag this stage as done
countered[m_currentStage] = 1;
// Advance the stage
m_currentStage++;
// Set the return value to true
ret = true;
// Check the next stage, if it is over 6 or equal to 0xFFFF flag the HO as complete
if (m_currentStage > 6 || m_wheel->abilities[m_currentStage] == 0xFFFF)
SetComplete(2);
}
}
}
return ret;
}
void HeroicOP::AddStarterChain(HeroicOPStarter* starter) {
starters.push_back(starter);
}
bool HeroicOP::ShiftWheel() {
// Can only shift once so if we already have return out
if (HasShifted())
return false;
// Clear the wheel
m_wheel = 0;
// Get a new Wheel
SetWheel(master_ho_list.GetWheel(starters.at(0)));
// Set the ho as shifted
m_shifted = true;
return true;
}
MasterHeroicOPList::MasterHeroicOPList() {
}
MasterHeroicOPList::~MasterHeroicOPList() {
map<int8, map<HeroicOPStarter*, vector<HeroicOPWheel*> > >::iterator itr;
map<HeroicOPStarter*, vector<HeroicOPWheel*> >::iterator itr2;
vector<HeroicOPWheel*>::iterator itr3;
vector<HeroicOPStarter*> temp;
vector<HeroicOPStarter*>::iterator itr4;
// loop through the m_hoList to delete the pointers
for (itr = m_hoList.begin(); itr != m_hoList.end(); itr++) {
// loop through the second map of the m_hoList
for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) {
// loop through the vector of the second map and delete the pointers
for (itr3 = itr2->second.begin(); itr3 != itr2->second.end(); itr3++)
safe_delete(*itr3);
// clear the vector
itr2->second.clear();
// put the starter in a temp list to delete later
temp.push_back(itr2->first);
}
// clear the seond map
itr->second.clear();
}
// clear the m_hoList
m_hoList.clear();
// Delete the starters
for (itr4 = temp.begin(); itr4 != temp.end(); itr4++)
safe_delete(*itr4);
// clear the temp vector
temp.clear();
}
void MasterHeroicOPList::AddStarter(int8 start_class, HeroicOPStarter* starter) {
if (m_hoList.count(start_class) == 0 || m_hoList[start_class].count(starter) == 0) {
m_hoList[start_class][starter]; // This adds the starter with out giving it a vector of wheels yet.
}
}
void MasterHeroicOPList::AddWheel(int32 starter_id, HeroicOPWheel* wheel) {
map<int8, map<HeroicOPStarter*, vector<HeroicOPWheel*> > >::iterator itr;
map<HeroicOPStarter*, vector<HeroicOPWheel*> >::iterator itr2;
bool found = false;
// Loop through the list and add the wheel to the correct starter
for (itr = m_hoList.begin(); itr != m_hoList.end(); itr++) {
for (itr2 = itr->second.begin(); itr2 != itr->second.end(); itr2++) {
if (itr2->first->id == starter_id) {
// Found a match, add the wheel, set the flag to break the first loop, and break this loop
itr2->second.push_back(wheel);
found = true;
break;
}
}
// If we found a match break the first loop
if (found)
break;
}
// No match found give an error.
if (!found)
LogWrite(SPELL__DEBUG, 0, "HO", "Attempted to add a wheel to a starter (%u) that doesn't exsist", starter_id);
}
HeroicOP* MasterHeroicOPList::GetHeroicOP(int8 class_id) {
if (m_hoList.count(class_id) == 0) {
LogWrite(SPELL__ERROR, 0, "HO", "No HO's found for the given class (%i)", class_id);
return 0;
}
map<HeroicOPStarter*, vector<HeroicOPWheel*> >::iterator itr;
HeroicOP* ret = new HeroicOP();
// Loop through the starters for this class and add them to the HO
for (itr = m_hoList[class_id].begin(); itr != m_hoList[class_id].end(); itr++)
ret->AddStarterChain(itr->first);
return ret;
}
HeroicOPWheel* MasterHeroicOPList::GetWheel(HeroicOPStarter* starter) {
if (!starter)
return 0;
if (m_hoList.count(starter->start_class) == 0) {
LogWrite(SPELL__ERROR, 0, "HO", "Start class (%u) not found", starter->start_class);
return 0;
}
if (m_hoList[starter->start_class].count(starter) == 0) {
LogWrite(SPELL__ERROR, 0, "HO", "Wheel not found for the provided starter (%u)", starter->id);
return 0;
}
int index = MakeRandomInt(0, m_hoList[starter->start_class][starter].size() - 1);
if(index < m_hoList[starter->start_class][starter].size())
return m_hoList[starter->start_class][starter].at(index);
else
LogWrite(SPELL__ERROR, 0, "HO", "Wheel index %u for heroic_ops starter ID %u NOT Found!! Wheel starter_class %u, wheel size: %u. Wheels that match starter_link_id -> Starter 'id' missing.", index, starter->id, starter->start_class, m_hoList[starter->start_class][starter].size());
return nullptr;
}

View File

@ -0,0 +1,156 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __HEROICOP_H__
#define __HEROICOP_H__
#include <map>
#include <vector>
#include "../../common/types.hpp"
using namespace std;
struct HeroicOPStarter {
int32 id;
int8 start_class;
int16 starter_icon;
int16 abilities[6];
};
struct HeroicOPWheel {
int8 order;
int16 shift_icon;
float chance;
int16 abilities[6];
int32 spell_id;
};
class HeroicOP {
public:
HeroicOP();
~HeroicOP();
/// <summary>Sets the complete flag for this Heroic OP</summary>
/// <param name='val'>The value to set the complete flag to, 1 = failed 2 = finished</param>
void SetComplete(int8 val) { m_complete = val; }
/// <summary>Sets the current stage of the starter chain or the wheel chain is at</summary>
/// <param name='val'>The stage to set this Heroic OP to</param>
void SetStage(int8 val) { m_currentStage = val; }
/// <summary>Sets the wheel for this Heroic OP</summary>
/// <param name='val'>The wheel we are setting the Heroic OP to</param>
void SetWheel(HeroicOPWheel* val);
/// <summary>Sets the start time for the wheel</summary>
/// <param name='val'>Value to set the start time to</param>
void SetStartTime(int32 val) { m_startTime = val; }
/// <summary>Sets the total time to complete the wheel</summary>
/// <param name='val'>Value to set the total time to</param>
void SetTotalTime(float val) { m_totalTime = val; }
/// <summary>Sets the target of this HO</summary>
/// <param name='val'>The ID of the spawn</param>
void SetTarget(int32 val);
/// <summary>Gets the complete flag for this Heroic OP</summary>
/// <returns>0 = not complete, 1 = complete, 2+= failed</returns>
int8 GetComplete() { return m_complete; }
/// <summary>Gets the wheel for this heroic op</summary>
HeroicOPWheel* GetWheel() { return m_wheel; }
/// <summary>Gets a pointer to the list of starter chains</summary>
vector<HeroicOPStarter*>* GetStarterChains() { return &starters; }
/// <summary>Gets the current stage the HO is on</summary>
int8 GetStage() { return m_currentStage; }
/// <summary>Gets the start time for the wheel</summary>
int32 GetStartTime() { return m_startTime; }
/// <summary>Gets the total time players have to complete the wheel</summary>
float GetTotalTime() { return m_totalTime; }
/// <summary>Gets the ID of this HO's target</summary>
int32 GetTarget() { return m_target; }
/// <summary></summary>
bool HasShifted() { return m_shifted; }
/// <summary>Checks to see if the given icon will advance the Heroic OP</summary>
/// <param name='icon'>The icon that is trying to advance the Heroic OP</param>
/// <returns>True if the icon advanced the HO</returns>
bool UpdateHeroicOP(int16 icon);
/// <summary>Reset the stage to 0</summary>
void ResetStage() { m_currentStage = 0; }
/// <summary>Adds a starter chain to the Heroic OP</summary>
/// <param name='starter'>The starter chain to add</param>
void AddStarterChain(HeroicOPStarter* starter);
/// <summary>Attempts to shift the wheel</summary>
bool ShiftWheel();
int8 countered[6];
private:
int8 m_complete;
int8 m_currentStage;
int32 m_startTime;
float m_totalTime;
int32 m_target;
bool m_shifted;
HeroicOPWheel* m_wheel;
vector<HeroicOPStarter*> starters;
};
class MasterHeroicOPList {
public:
MasterHeroicOPList();
~MasterHeroicOPList();
/// <summary>Adds the starter chain to the list</summary>
/// <param name='start_class'>Class id for the starter chain</param>
/// <param name='starter'>Starter chain to add</param>
void AddStarter(int8 start_class, HeroicOPStarter* starter);
/// <summary>Add the wheel chain to the list</summary>
/// <param name='starter_id'>Id of the starter this wheel belongs to</param>
/// <param name='wheel'>Wheel to add</param>
void AddWheel(int32 starter_id, HeroicOPWheel* wheel);
/// <summary>Creates a new HO</summary>
/// <param name='class_id'>Class ID starting the HO</param>
HeroicOP* GetHeroicOP(int8 class_id);
/// <summary>Gets a random wheel from the given starter</summary>
/// <param name='starter'>The starter to determine what wheels to choose from</param>
HeroicOPWheel* GetWheel(HeroicOPStarter* starter);
private:
// map<class, map<starter, vector<wheel> > >
map<int8, map<HeroicOPStarter*, vector<HeroicOPWheel*> > > m_hoList;
};
#endif

View File

@ -0,0 +1,79 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../Worlddatabase.hpp"
#include "../../common/Log.h"
#include "HeroicOp.h"
extern MasterHeroicOPList master_ho_list;
void WorldDatabase::LoadHOStarters() {
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `id`, `starter_class`, `starter_icon`, `ability1`, `ability2`, `ability3`, `ability4`, `ability5`, `ability6` FROM `heroic_ops` WHERE `ho_type`='Starter'");
if (result && mysql_num_rows(result) > 0) {
int32 count = 0;
while ((row = mysql_fetch_row(result))) {
HeroicOPStarter* starter = new HeroicOPStarter;
starter->id = atoul(row[0]);
starter->start_class = atoi(row[1]);
starter->starter_icon = atoi(row[2]);
starter->abilities[0] = atoi(row[3]);
starter->abilities[1] = atoi(row[4]);
starter->abilities[2] = atoi(row[5]);
starter->abilities[3] = atoi(row[6]);
starter->abilities[4] = atoi(row[7]);
starter->abilities[5] = atoi(row[8]);
master_ho_list.AddStarter(starter->start_class, starter);
count++;
}
LogWrite(WORLD__INFO, 0, "World", "- Loaded %u starter chains", count);
}
}
void WorldDatabase::LoadHOWheel() {
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT `starter_link_id`, `chain_order`, `shift_icon`, `spell_id`, `chance`, `ability1`, `ability2`, `ability3`, `ability4`, `ability5`, `ability6` FROM `heroic_ops` WHERE `ho_type`='Wheel'");
if (result && mysql_num_rows(result) > 0) {
int32 count = 0;
while ((row = mysql_fetch_row(result))) {
HeroicOPWheel* wheel = new HeroicOPWheel;
wheel->order = atoi(row[1]);
wheel->shift_icon = atoi(row[2]);
wheel->spell_id = atoul(row[3]);
wheel->chance = atof(row[4]);
wheel->abilities[0] = atoi(row[5]);
wheel->abilities[1] = atoi(row[6]);
wheel->abilities[2] = atoi(row[7]);
wheel->abilities[3] = atoi(row[8]);
wheel->abilities[4] = atoi(row[9]);
wheel->abilities[5] = atoi(row[10]);
master_ho_list.AddWheel(atoul(row[0]), wheel);
count++;
}
LogWrite(WORLD__INFO, 0, "World", "- Loaded %u HO wheels", count);
}
}

View File

@ -0,0 +1,158 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../ClientPacketFunctions.h"
#include "../../common/Log.h"
#include "HeroicOp.h"
#include "../Spells.h"
extern ConfigReader configReader;
extern MasterSpellList master_spell_list;
void ClientPacketFunctions::SendHeroicOPUpdate(Client* client, HeroicOP* ho) {
if (!client || !client->GetPlayer()) {
LogWrite(PACKET__ERROR, 0, "Packets", "SendHeroicOPUpdate() called without a valid client");
return;
}
if (!ho) {
LogWrite(PACKET__ERROR, 0, "Packets", "SendHeroicOPUpdate() called without a valid HO");
return;
}
PacketStruct* packet = configReader.getStruct("WS_HeroicOpportunity", client->GetVersion());
Spell* spell = 0;
if (packet) {
packet->setDataByName("id", client->GetPlayer()->GetIDWithPlayerSpawn(client->GetPlayer()));
if (ho->GetWheel()) {
spell = master_spell_list.GetSpell(ho->GetWheel()->spell_id, 1);
if (!spell) {
LogWrite(SPELL__ERROR, 0, "HO", "Unable to get the spell (%u)", ho->GetWheel()->spell_id);
return;
}
packet->setDataByName("name", spell->GetName());
packet->setDataByName("description", spell->GetDescription());
packet->setDataByName("order", ho->GetWheel()->order);
packet->setDataByName("time_total", ho->GetTotalTime());
packet->setDataByName("time_left", max(0.0f, (float)(((ho->GetStartTime() + (ho->GetTotalTime() * 1000)) - Timer::GetCurrentTime2()) / 1000)));
// This is not displayed in the wheel so set it to 0xFFFF
packet->setDataByName("starter_icon", 0xFFFF);
if (ho->HasShifted())
packet->setDataByName("shift_icon", 0xFFFF);
else
packet->setDataByName("shift_icon", ho->GetWheel()->shift_icon);
// If completed set special values
if (ho->GetComplete() > 0) {
packet->setDataByName("wheel_type", 2);
packet->setDataByName("unknown", ho->GetComplete());
}
char temp[20];
char ability[20];
// Set the icons for the whee;
for (int8 i = 1; i < 7; i++) {
strcpy(ability, "icon");
itoa(i, temp, 10);
strcat(ability, temp);
packet->setDataByName(ability, ho->GetWheel()->abilities[i-1]);
}
// Flag the icons that are completed
for (int8 i = 1; i < 7; i++) {
strcpy(ability, "countered");
itoa(i, temp, 10);
strcat(ability, temp);
packet->setDataByName(ability, ho->countered[i-1]);
}
}
else {
if (ho->GetComplete() > 0) {
// This will make the ui element vanish
packet->setDataByName("wheel_type", 5);
packet->setDataByName("unknown", 8);
}
else {
packet->setDataByName("wheel_type", 4);
}
packet->setDataByName("icon1", 0xFFFF);
packet->setDataByName("icon2", 0xFFFF);
packet->setDataByName("icon3", 0xFFFF);
packet->setDataByName("icon4", 0xFFFF);
packet->setDataByName("icon5", 0xFFFF);
packet->setDataByName("icon6", 0xFFFF);
packet->setDataByName("shift_icon", 0xFFFF);
int8 index = 1;
char temp[20];
char ability[20];
vector<HeroicOPStarter*>::iterator itr;
for (itr = ho->GetStarterChains()->begin(); itr != ho->GetStarterChains()->end(); itr++, index++) {
if (index > 6 )
break;
strcpy(ability, "icon");
itoa(index, temp, 10);
strcat(ability, temp);
packet->setDataByName(ability, (*itr)->abilities[ho->GetStage()]);
// Only set this once
if (index == 1)
packet->setDataByName("starter_icon", (*itr)->starter_icon);
}
}
client->QueuePacket(packet->serialize());
}
safe_delete(packet);
}
/*
<Struct Name="WS_HeroicOpportunity" ClientVersion="1" OpcodeName="OP_UpdateOpportunityMsg">
<Data ElementName="name" Type="EQ2_16Bit_String" />
<Data ElementName="description" Type="EQ2_16Bit_String" />
<Data ElementName="id" Type="int32" />
<Data ElementName="wheel_type" Type="int8" />
<Data ElementName="unknown" Type="int8" />
<Data ElementName="order" Type="int8" />
<Data ElementName="shift_icon" Type="int16" />
<Data ElementName="starter_icon" Type="int16" />
<Data ElementName="time_total" Type="float" />
<Data ElementName="time_left" Type="float" />
<Data ElementName="icon1" Type="int16" />
<Data ElementName="icon2" Type="int16" />
<Data ElementName="icon3" Type="int16" />
<Data ElementName="icon4" Type="int16" />
<Data ElementName="icon5" Type="int16" />
<Data ElementName="icon6" Type="int16" />
<Data ElementName="countered1" Type="int16" />
<Data ElementName="countered2" Type="int16" />
<Data ElementName="countered3" Type="int16" />
<Data ElementName="countered4" Type="int16" />
<Data ElementName="countered5" Type="int16" />
<Data ElementName="countered6" Type="int16" />
</Struct>
*/

View File

@ -0,0 +1,131 @@
#include "../Worlddatabase.hpp"
#include "../World.h"
extern World world;
void WorldDatabase::LoadHouseZones() {
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT * FROM `houses`");
if (result && mysql_num_rows(result) > 0) {
while ((row = mysql_fetch_row(result))) {
world.AddHouseZone(atoul(row[0]), row[1], atoi64(row[2]), atoul(row[3]), atoi64(row[4]), atoul(row[5]), atoi(row[6]), atoi(row[7]), atoi(row[8]), atoul(row[9]), atoul(row[10]), atof(row[11]), atof(row[12]), atof(row[13]), atof(row[14]));
}
}
}
int64 WorldDatabase::AddPlayerHouse(int32 char_id, int32 house_id, int32 instance_id, int32 upkeep_due) {
Query query;
string insert = string("INSERT INTO character_houses (char_id, house_id, instance_id, upkeep_due) VALUES (%u, %u, %u, %u) ");
query.RunQuery2(Q_INSERT, insert.c_str(), char_id, house_id, instance_id, upkeep_due);
int64 unique_id = query.GetLastInsertedID();
return unique_id;
}
void WorldDatabase::SetHouseUpkeepDue(int32 char_id, int32 house_id, int32 instance_id, int32 upkeep_due) {
Query query;
string update = string("UPDATE character_houses set upkeep_due=%u where char_id = %u and house_id = %u and instance_id = %u");
query.RunQuery2(Q_UPDATE, update.c_str(), upkeep_due, char_id, house_id, instance_id);
}
void WorldDatabase::UpdateHouseEscrow(int32 house_id, int32 instance_id, int64 amount_coins, int32 amount_status) {
Query query;
string update = string("UPDATE character_houses set escrow_coins = %llu, escrow_status = %u where house_id = %u and instance_id = %u");
query.RunQuery2(Q_UPDATE, update.c_str(), amount_coins, amount_status, house_id, instance_id);
}
void WorldDatabase::RemovePlayerHouse(int32 char_id, int32 house_id) {
}
void WorldDatabase::LoadPlayerHouses() {
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "SELECT h.id, h.char_id, h.house_id, h.instance_id, h.upkeep_due, h.escrow_coins, h.escrow_status, c.name FROM character_houses h, characters c WHERE h.char_id = c.id");
if (result && mysql_num_rows(result) > 0) {
while ((row = mysql_fetch_row(result))) {
world.AddPlayerHouse(atoul(row[1]), atoul(row[2]), atoi64(row[0]), atoul(row[3]), atoul(row[4]), atoi64(row[5]), atoul(row[6]), row[7]);
}
}
}
void WorldDatabase::LoadDeposits(PlayerHouse* ph)
{
if (!ph)
return;
ph->deposits.clear();
ph->depositsMap.clear();
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select timestamp, amount, last_amount, status, last_status, name from character_house_deposits where house_id = %u and instance_id = %u order by timestamp asc limit 255", ph->house_id, ph->instance_id);
if (result && mysql_num_rows(result) > 0) {
while ((row = mysql_fetch_row(result))) {
Deposit d;
d.timestamp = atoul(row[0]);
int64 outVal = strtoull(row[1], NULL, 0);
d.amount = outVal;
outVal = strtoull(row[2], NULL, 0);
d.last_amount = outVal;
d.status = atoul(row[3]);
d.last_status = atoul(row[4]);
d.name = string(row[5]);
ph->deposits.push_back(d);
ph->depositsMap.insert(make_pair(d.name, d));
}
}
}
void WorldDatabase::LoadHistory(PlayerHouse* ph)
{
if (!ph)
return;
ph->history.clear();
Query query;
MYSQL_ROW row;
MYSQL_RES* result = query.RunQuery2(Q_SELECT, "select timestamp, amount, status, reason, name, pos_flag from character_house_history where house_id = %u and instance_id = %u order by timestamp asc limit 255", ph->house_id, ph->instance_id);
if (result && mysql_num_rows(result) > 0) {
while ((row = mysql_fetch_row(result))) {
HouseHistory h;
h.timestamp = atoul(row[0]);
int64 outVal = strtoull(row[1], NULL, 0);
h.amount = outVal;
h.status = atoul(row[2]);
h.reason = string(row[3]);
h.name = string(row[4]);
h.pos_flag = atoul(row[5]);
ph->history.push_back(h);
}
}
}
void WorldDatabase::AddHistory(PlayerHouse* house, char* name, char* reason, int32 timestamp, int64 amount, int32 status, int8 pos_flag)
{
if (!house)
return;
HouseHistory h(Timer::GetUnixTimeStamp(), amount, string(name), string(reason), status, pos_flag);
house->history.push_back(h);
Query query;
string insert = string("INSERT INTO character_house_history (timestamp, house_id, instance_id, name, amount, status, reason, pos_flag) VALUES (%u, %u, %u, '%s', %llu, %u, '%s', %u) ");
query.RunQuery2(Q_INSERT, insert.c_str(), timestamp, house->house_id, house->instance_id, name, amount, status, reason, pos_flag);
}

View File

@ -0,0 +1,454 @@
#include "../ClientPacketFunctions.h"
#include "../World.h"
#include "../client.h"
#include "../Worlddatabase.hpp"
#include "../Rules/Rules.h"
extern ConfigReader configReader;
extern World world;
extern WorldDatabase database;
extern RuleManager rule_manager;
void ClientPacketFunctions::SendHousePurchase(Client* client, HouseZone* hz, int32 spawnID) {
PacketStruct* packet = configReader.getStruct("WS_PlayerHousePurchase", client->GetVersion());
if (packet) {
int8 disable_alignment_req = rule_manager.GetZoneRule(client->GetCurrentZoneID(), R_Player, DisableHouseAlignmentRequirement)->GetInt8();
packet->setDataByName("house_name", hz->name.c_str());
packet->setDataByName("house_id", hz->id);
packet->setDataByName("spawn_id", spawnID);
packet->setDataByName("purchase_coins", hz->cost_coin);
packet->setDataByName("purchase_status", hz->cost_status);
packet->setDataByName("upkeep_coins", hz->upkeep_coin);
packet->setDataByName("upkeep_status", hz->upkeep_status);
packet->setDataByName("vendor_vault_slots", hz->vault_slots);
string req;
if (hz->alignment > 0 && !disable_alignment_req) {
req = "You must be of ";
if (hz->alignment == 1)
req.append("Good");
else
req.append("Evil");
req.append(" alignment");
}
if (hz->guild_level > 0) {
if (req.length() > 0) {
req.append(", and a guild level of ");
char temp[5];
sprintf(temp, "%i", hz->guild_level);
req.append(temp);
//req.append(std::to_string(static_cast<long long>(hz->guild_level)));
}
else {
req.append("Requires a guild of level ");
char temp[5];
sprintf(temp, "%i", hz->guild_level);
req.append(temp);
//req.append(std::to_string(static_cast<long long>(hz->guild_level)))
req.append(" or above");
}
}
if (req.length() > 0) {
req.append(" in order to purchase a home within the ");
req.append(hz->name);
req.append(".");
}
packet->setDataByName("additional_reqs", req.c_str());
bool enable_buy = true;
if (hz->alignment > 0 && client->GetPlayer()->GetAlignment() != hz->alignment && !disable_alignment_req)
enable_buy = false;
if (hz->guild_level > 0 && (!client->GetPlayer()->GetGuild() || (client->GetPlayer()->GetGuild() && client->GetPlayer()->GetGuild()->GetLevel() < hz->guild_level)))
enable_buy = false;
packet->setDataByName("enable_buy", enable_buy ? 1 : 0);
//packet->PrintPacket();
client->QueuePacket(packet->serialize());
}
safe_delete(packet);
}
void ClientPacketFunctions::SendHousingList(Client* client) {
if(client->GetVersion() <= 561) {
return; // not supported
}
std::vector<PlayerHouse*> houses = world.GetAllPlayerHouses(client->GetCharacterID());
// this packet must be sent first otherwise it blocks out the enter house option after paying upkeep
PacketStruct* packet = configReader.getStruct("WS_CharacterHousingList", client->GetVersion());
if(!packet) {
return;
}
packet->setArrayLengthByName("num_houses", houses.size());
for (int i = 0; i < houses.size(); i++)
{
PlayerHouse* ph = (PlayerHouse*)houses[i];
HouseZone* hz = world.GetHouseZone(ph->house_id);
string name;
name = ph->player_name;
name.append("'s ");
name.append(hz->name);
packet->setArrayDataByName("house_id", ph->unique_id, i);
string zone_name = database.GetZoneName(hz->zone_id);
if(zone_name.length() > 0)
packet->setArrayDataByName("zone", zone_name.c_str(), i);
packet->setArrayDataByName("house_city", hz->name.c_str(), i);
packet->setArrayDataByName("house_address", "", i); // need this pulled from live
packet->setArrayDataByName("house_description", name.c_str(), i);
packet->setArrayDataByName("index", i, i); // they send 2, 4, 6, 8 as the index ID's on the client..
// this seems to be some kind of timestamp, if we keep updating then in conjunction with upkeep_due
// in SendBaseHouseWindow/WS_PlayerHouseBaseScreen being a >0 number we can access 'enter house'
int32 upkeep_due = 0;
if (((sint64)ph->upkeep_due - (sint64)Timer::GetUnixTimeStamp()) > 0)
upkeep_due = ph->upkeep_due - Timer::GetUnixTimeStamp();
if ( client->GetVersion() >= 63119 )
packet->setArrayDataByName("unknown2a", 0xFFFFFFFF, i);
else
packet->setArrayDataByName("unknown2", 0xFFFFFFFF, i);
}
client->QueuePacket(packet->serialize());
safe_delete(packet);
}
void ClientPacketFunctions::SendBaseHouseWindow(Client* client, HouseZone* hz, PlayerHouse* ph, int32 spawnID) {
// if we don't send this then the enter house option won't be available if upkeep is paid
if (!hz || !ph)
{
client->SimpleMessage(CHANNEL_COLOR_RED, "HouseZone or PlayerHouse missing and cannot send SendBaseHouseWindow");
return;
}
string name;
name = ph->player_name;
name.append("'s ");
name.append(hz->name);
if (spawnID)
SendHousingList(client);
int32 upkeep_due = 0;
if (((sint64)ph->upkeep_due - (sint64)Timer::GetUnixTimeStamp()) > 0)
upkeep_due = ph->upkeep_due - Timer::GetUnixTimeStamp();
// need this to enable the "enter house" button
PacketStruct* packet = nullptr;
if(client->GetVersion() > 561 && client->GetCurrentZone()->GetInstanceType() != PERSONAL_HOUSE_INSTANCE
&& client->GetCurrentZone()->GetInstanceType() != GUILD_HOUSE_INSTANCE) {
packet = configReader.getStruct("WS_UpdateHouseAccessDataMsg", client->GetVersion());
if(!packet) {
return; // we need this for these clients or enter house will not work properly
}
if (packet) {
packet->setDataByName("house_id", 0xFFFFFFFFFFFFFFFF);
packet->setDataByName("success", (upkeep_due > 0) ? 0xFFFFFFFF : 0);
packet->setDataByName("unknown2", 0xFFFFFFFF);
packet->setDataByName("unknown3", 0xFFFFFFFF);
}
client->QueuePacket(packet->serialize());
}
safe_delete(packet);
packet = configReader.getStruct("WS_PlayerHouseBaseScreen", client->GetVersion());
if (packet) {
packet->setDataByName("house_id", ph->unique_id);
packet->setDataByName("spawn_id", spawnID);
packet->setDataByName("character_id", client->GetPlayer()->GetCharacterID());
packet->setDataByName("house_name", name.c_str());
packet->setDataByName("zone_name", hz->name.c_str());
packet->setDataByName("upkeep_cost_coins", hz->upkeep_coin);
packet->setDataByName("upkeep_cost_status", hz->upkeep_status);
packet->setDataByName("upkeep_due", upkeep_due);
packet->setDataByName("escrow_balance_coins", ph->escrow_coins);
packet->setDataByName("escrow_balance_status", ph->escrow_status);
// temp - set priv level to owner for now
packet->setDataByName("privlage_level", 4);
// temp - set house type to personal house for now
packet->setDataByName("house_type", 0);
if(client->GetCurrentZone()->GetInstanceType() == PERSONAL_HOUSE_INSTANCE
|| client->GetCurrentZone()->GetInstanceType() == GUILD_HOUSE_INSTANCE) {
packet->setDataByName("inside_house", 1);
packet->setDataByName("public_access_level", 1);
}
packet->setDataByName("num_access", 0);
packet->setDataByName("num_history", 0);
// allows deposits/history to be seen -- at this point seems plausible supposed to be 'inside_house'..?
packet->setDataByName("unknown3", (ph->deposits.size() || ph->history.size()) ? 1 : 0);
packet->setArrayLengthByName("num_deposit", ph->deposits.size());
list<Deposit>::iterator itr;
int d = 0;
for (itr = ph->deposits.begin(); itr != ph->deposits.end(); itr++)
{
packet->setArrayDataByName("deposit_name", itr->name.c_str(), d);
packet->setArrayDataByName("deposit_total_coin", itr->amount, d);
packet->setArrayDataByName("deposit_time_stamp", itr->timestamp, d);
packet->setArrayDataByName("deposit_last_coin", itr->last_amount, d);
packet->setArrayDataByName("deposit_total_status", itr->status, d);
packet->setArrayDataByName("deposit_last_status", itr->last_status, d);
d++;
}
packet->setArrayLengthByName("num_history", ph->history.size());
list<HouseHistory>::iterator hitr;
d = 0;
for (hitr = ph->history.begin(); hitr != ph->history.end(); hitr++)
{
packet->setArrayDataByName("history_name", hitr->name.c_str(), d);
packet->setArrayDataByName("history_coins", hitr->amount, d);
packet->setArrayDataByName("history_status", hitr->status, d);
packet->setArrayDataByName("history_time_stamp", hitr->timestamp, d);
packet->setArrayDataByName("history_reason", hitr->reason.c_str(), d);
packet->setArrayDataByName("history_add_flag", hitr->pos_flag, d);
d++;
}
EQ2Packet* pack = packet->serialize();
//DumpPacket(pack);
client->QueuePacket(pack);
}
safe_delete(packet);
}
void ClientPacketFunctions::SendHouseVisitWindow(Client* client, vector<PlayerHouse*> houses) {
PacketStruct* packet = configReader.getStruct("WS_DisplayVisitScreen", client->GetVersion());
if (packet) {
vector<PlayerHouse*>::iterator itr;
packet->setArrayLengthByName("num_houses", houses.size());
int16 i = 0;
for (itr = houses.begin(); itr != houses.end(); itr++) {
PlayerHouse* ph = *itr;
if (ph) {
HouseZone* hz = world.GetHouseZone(ph->house_id);
if (hz) {
packet->setArrayDataByName("house_id", ph->unique_id, i);
packet->setArrayDataByName("house_owner", ph->player_name.c_str(), i);
packet->setArrayDataByName("house_location", hz->name.c_str(), i);
packet->setArrayDataByName("house_zone", client->GetCurrentZone()->GetZoneName(), i);
if ( string(client->GetPlayer()->GetName()).compare(ph->player_name) == 0 )
packet->setArrayDataByName("access_level", 4, i);
else
packet->setArrayDataByName("access_level", 1, i);
packet->setArrayDataByName("visit_flag", 0, i); // 0 = allowed to visit, 1 = owner hasn't paid upkeep
i++;
}
}
}
client->QueuePacket(packet->serialize());
}
safe_delete(packet);
}
/*
<Struct Name="WS_DisplayVisitScreen" ClientVersion="1193" OpcodeName="OP_DisplayInnVisitScreenMsg">
<Data ElementName="num_houses" Type="int16" Size="1" />
<Data ElementName="visithouse_array" Type="Array" ArraySizeVariable="num_houses">
<Data ElementName="house_id" Type="int64" />
<Data ElementName="house_owner" Type="EQ2_16Bit_String" />
<Data ElementName="house_location" Type="EQ2_16Bit_string" />
<Data ElementName="house_zone" Type="EQ2_16Bit_String" />
<Data ElementName="access_level" Type="int8" Size="1" />
<Data ElementName="unknown3" Type="int8" Size="3" />
<Data ElementName="visit_flag" Type="int8" Size="1" />
</Data>
<Data ElementName="unknown4" Type="int32" Size="1" />
<Data ElementName="unknown5" Type="int8" Size="1" />
</Struct>
*/
void ClientPacketFunctions::SendLocalizedTextMessage(Client* client)
{
/***
-- OP_ReloadLocalizedTxtMsg --
5/26/2020 19:08:41
69.174.200.100 -> 192.168.1.1
0000: 01 FF 63 01 62 00 00 00 1C 00 49 72 6F 6E 74 6F ..c.b.....Ironto
0010: 65 73 20 45 61 73 74 20 4C 61 72 67 65 20 49 6E es East Large In
0020: 6E 20 52 6F 6F 6D 07 01 00 00 00 1C 00 49 72 6F n Room.......Iro
0030: 6E 74 6F 65 73 20 45 61 73 74 20 4C 61 72 67 65 ntoes East Large
0040: 20 49 6E 6E 20 52 6F 6F 6D 07 02 00 00 00 1C 00 Inn Room.......
0050: 49 72 6F 6E 74 6F 65 73 20 45 61 73 74 20 4C 61 Irontoes East La
0060: 72 67 65 20 49 6E 6E 20 52 6F 6F 6D 07 03 00 00 rge Inn Room....
0070: 00 1C 00 49 72 6F 6E 74 6F 65 73 20 45 61 73 74 ...Irontoes East
0080: 20 4C 61 72 67 65 20 49 6E 6E 20 52 6F 6F 6D 07 Large Inn Room.
0090: 04 00 00 00 1C 00 49 72 6F 6E 74 6F 65 73 20 45 ......Irontoes E
00A0: 61 73 74 20 4C 61 72 67 65 20 49 6E 6E 20 52 6F ast Large Inn Ro
00B0: 6F 6D 07 05 00 00 00 1C 00 49 72 6F 6E 74 6F 65 om.......Irontoe
00C0: 73 20 45 61 73 74 20 4C 61 72 67 65 20 49 6E 6E s East Large Inn
00D0: 20 52 6F 6F 6D 07 06 00 00 00 1C 00 49 72 6F 6E Room.......Iron
00E0: 74 6F 65 73 20 45 61 73 74 20 4C 61 72 67 65 20 toes East Large
00F0: 49 6E 6E 20 52 6F 6F 6D 07 07 00 00 00 19 00 51 Inn Room.......Q
0100: 65 79 6E 6F 73 20 47 75 69 6C 64 20 48 61 6C 6C eynos Guild Hall
0110: 2C 20 54 69 65 72 20 31 07 08 00 00 00 16 00 4C , Tier 1.......L
0120: 69 6F 6E 27 73 20 4D 61 6E 65 20 53 75 69 74 65 ion's Mane Suite
0130: 20 52 6F 6F 6D 07 09 00 00 00 16 00 4C 69 6F 6E Room.......Lion
0140: 27 73 20 4D 61 6E 65 20 53 75 69 74 65 20 52 6F 's Mane Suite Ro
0150: 6F 6D 07 0A 00 00 00 16 00 4C 69 6F 6E 27 73 20 om.......Lion's
0160: 4D 61 6E 65 20 53 75 69 74 65 20 52 6F 6F 6D 07 Mane Suite Room.
0170: 0B 00 00 00 16 00 4C 69 6F 6E 27 73 20 4D 61 6E ......Lion's Man
0180: 65 20 53 75 69 74 65 20 52 6F 6F 6D 07 0C 00 00 e Suite Room....
0190: 00 0E 00 32 20 4C 75 63 69 65 20 53 74 72 65 65 ...2 Lucie Stree
01A0: 74 07 0D 00 00 00 0F 00 31 37 20 54 72 61 6E 71 t.......17 Tranq
01B0: 75 69 6C 20 57 61 79 07 0E 00 00 00 0E 00 38 20 uil Way.......8
01C0: 4C 75 63 69 65 20 53 74 72 65 65 74 07 0F 00 00 Lucie Street....
01D0: 00 0F 00 31 32 20 4C 75 63 69 65 20 53 74 72 65 ...12 Lucie Stre
01E0: 65 74 07 10 00 00 00 0F 00 31 38 20 4C 75 63 69 et.......18 Luci
01F0: 65 20 53 74 72 65 65 74 07 11 00 00 00 0F 00 32 e Street.......2
0200: 30 20 4C 75 63 69 65 20 53 74 72 65 65 74 07 12 0 Lucie Street..
0210: 00 00 00 0E 00 33 20 54 72 61 6E 71 75 69 6C 20 .....3 Tranquil
0220: 57 61 79 07 13 00 00 00 0E 00 37 20 54 72 61 6E Way.......7 Tran
0230: 71 75 69 6C 20 57 61 79 07 14 00 00 00 0F 00 31 quil Way.......1
0240: 33 20 54 72 61 6E 71 75 69 6C 20 57 61 79 07 15 3 Tranquil Way..
0250: 00 00 00 0F 00 31 35 20 54 72 61 6E 71 75 69 6C .....15 Tranquil
0260: 20 57 61 79 07 16 00 00 00 19 00 51 65 79 6E 6F Way.......Qeyno
0270: 73 20 47 75 69 6C 64 20 48 61 6C 6C 2C 20 54 69 s Guild Hall, Ti
0280: 65 72 20 32 07 17 00 00 00 0F 00 38 20 45 72 6F er 2.......8 Ero
0290: 6C 6C 69 73 69 20 4C 61 6E 65 07 18 00 00 00 0F llisi Lane......
02A0: 00 35 20 45 72 6F 6C 6C 69 73 69 20 4C 61 6E 65 .5 Erollisi Lane
02B0: 07 19 00 00 00 0E 00 35 20 4B 61 72 61 6E 61 20 .......5 Karana
02C0: 43 6F 75 72 74 07 1A 00 00 00 0D 00 32 20 42 61 Court.......2 Ba
02D0: 79 6C 65 20 43 6F 75 72 74 07 1B 00 00 00 0D 00 yle Court.......
02E0: 34 20 42 61 79 6C 65 20 43 6F 75 72 74 07 1C 00 4 Bayle Court...
02F0: 00 00 16 00 4C 69 6F 6E 27 73 20 4D 61 6E 65 20 ....Lion's Mane
0300: 53 75 69 74 65 20 52 6F 6F 6D 07 1D 00 00 00 16 Suite Room......
0310: 00 4C 69 6F 6E 27 73 20 4D 61 6E 65 20 53 75 69 .Lion's Mane Sui
0320: 74 65 20 52 6F 6F 6D 07 1E 00 00 00 16 00 4C 69 te Room.......Li
0330: 6F 6E 27 73 20 4D 61 6E 65 20 53 75 69 74 65 20 on's Mane Suite
0340: 52 6F 6F 6D 07 1F 00 00 00 16 00 4C 69 6F 6E 27 Room.......Lion'
0350: 73 20 4D 61 6E 65 20 53 75 69 74 65 20 52 6F 6F s Mane Suite Roo
0360: 6D 07 20 00 00 00 0E 00 35 20 4C 75 63 69 65 20 m. .....5 Lucie
0370: 53 74 72 65 65 74 07 21 00 00 00 0F 00 32 30 20 Street.!.....20
0380: 4B 61 72 61 6E 61 20 43 6F 75 72 74 07 22 00 00 Karana Court."..
0390: 00 0E 00 39 20 4C 75 63 69 65 20 53 74 72 65 65 ...9 Lucie Stree
03A0: 74 07 23 00 00 00 0F 00 31 35 20 4C 75 63 69 65 t.#.....15 Lucie
03B0: 20 53 74 72 65 65 74 07 24 00 00 00 0F 00 31 37 Street.$.....17
03C0: 20 4C 75 63 69 65 20 53 74 72 65 65 74 07 25 00 Lucie Street.%.
03D0: 00 00 0F 00 32 31 20 4C 75 63 69 65 20 53 74 72 ....21 Lucie Str
03E0: 65 65 74 07 26 00 00 00 0E 00 36 20 4B 61 72 61 eet.&.....6 Kara
03F0: 6E 61 20 43 6F 75 72 74 07 27 00 00 00 0F 00 31 na Court.'.....1
0400: 32 20 4B 61 72 61 6E 61 20 43 6F 75 72 74 07 28 2 Karana Court.(
0410: 00 00 00 0F 00 31 34 20 4B 61 72 61 6E 61 20 43 .....14 Karana C
0420: 6F 75 72 74 07 29 00 00 00 0F 00 31 38 20 4B 61 ourt.).....18 Ka
0430: 72 61 6E 61 20 43 6F 75 72 74 07 2A 00 00 00 1E rana Court.*....
0440: 00 43 6F 6E 63 6F 72 64 69 75 6D 20 54 6F 77 65 .Concordium Towe
0450: 72 20 4D 61 67 69 63 61 6C 20 4D 61 6E 6F 72 07 r Magical Manor.
0460: 2B 00 00 00 15 00 41 72 63 61 6E 65 20 41 63 61 +.....Arcane Aca
0470: 64 65 6D 79 20 50 6F 72 74 61 6C 07 2C 00 00 00 demy Portal.,...
0480: 13 00 43 6F 75 72 74 20 6F 66 20 74 68 65 20 4D ..Court of the M
0490: 61 73 74 65 72 07 2D 00 00 00 13 00 43 69 74 79 aster.-.....City
04A0: 20 6F 66 20 4D 69 73 74 20 45 73 74 61 74 65 07 of Mist Estate.
04B0: 2E 00 00 00 10 00 44 61 72 6B 6C 69 67 68 74 20 ......Darklight
04C0: 50 61 6C 61 63 65 07 2F 00 00 00 11 00 44 65 65 Palace./.....Dee
04D0: 70 77 61 74 65 72 20 52 65 74 72 65 61 74 07 30 pwater Retreat.0
04E0: 00 00 00 24 00 44 68 61 6C 67 61 72 20 50 72 65 ...$.Dhalgar Pre
04F0: 63 69 70 69 63 65 20 6F 66 20 74 68 65 20 44 65 cipice of the De
0500: 65 70 20 50 6F 72 74 61 6C 07 31 00 00 00 12 00 ep Portal.1.....
0510: 44 69 6D 65 6E 73 69 6F 6E 61 6C 20 50 6F 63 6B Dimensional Pock
0520: 65 74 07 32 00 00 00 0B 00 44 6F 6A 6F 20 50 6F et.2.....Dojo Po
0530: 72 74 61 6C 07 33 00 00 00 21 00 45 6C 61 62 6F rtal.3...!.Elabo
0540: 72 61 74 65 20 45 73 74 61 74 65 20 6F 66 20 55 rate Estate of U
0550: 6E 72 65 73 74 20 50 6F 72 74 61 6C 07 34 00 00 nrest Portal.4..
0560: 00 11 00 45 74 68 65 72 6E 65 72 65 20 45 6E 63 ...Ethernere Enc
0570: 6C 61 76 65 07 35 00 00 00 10 00 45 76 65 72 66 lave.5.....Everf
0580: 72 6F 73 74 20 50 6F 72 74 61 6C 07 36 00 00 00 rost Portal.6...
0590: 16 00 46 65 61 72 66 75 6C 20 52 65 74 72 65 61 ..Fearful Retrea
05A0: 74 20 50 6F 72 74 61 6C 07 37 00 00 00 0F 00 46 t Portal.7.....F
05B0: 65 6C 77 69 74 68 65 20 50 6F 72 74 61 6C 07 38 elwithe Portal.8
05C0: 00 00 00 10 00 46 72 65 65 62 6C 6F 6F 64 20 50 .....Freeblood P
05D0: 6F 72 74 61 6C 07 39 00 00 00 0C 00 46 72 69 67 ortal.9.....Frig
05E0: 68 74 20 4D 61 6E 6F 72 07 3A 00 00 00 11 00 47 ht Manor.:.....G
05F0: 61 6C 6C 65 6F 6E 20 6F 66 20 44 72 65 61 6D 73 alleon of Dreams
0600: 07 3B 00 00 00 14 00 48 61 6C 6C 20 6F 66 20 74 .;.....Hall of t
0610: 68 65 20 43 68 61 6D 70 69 6F 6E 07 3C 00 00 00 he Champion.<...
0620: 10 00 48 75 61 20 4D 65 69 6E 20 52 65 74 72 65 ..Hua Mein Retre
0630: 61 74 07 3D 00 00 00 1C 00 49 73 6C 65 20 6F 66 at.=.....Isle of
0640: 20 52 65 66 75 67 65 20 50 72 65 73 74 69 67 65 Refuge Prestige
0650: 20 48 6F 6D 65 07 3E 00 00 00 0F 00 4B 65 72 61 Home.>.....Kera
0660: 66 79 72 6D 27 73 20 4C 61 69 72 07 3F 00 00 00 fyrm's Lair.?...
0670: 0E 00 4B 72 6F 6D 7A 65 6B 20 50 6F 72 74 61 6C ..Kromzek Portal
0680: 07 40 00 00 00 10 00 4C 61 76 61 73 74 6F 72 6D .@.....Lavastorm
0690: 20 50 6F 72 74 61 6C 07 41 00 00 00 0E 00 4C 69 Portal.A.....Li
06A0: 62 72 61 72 79 20 50 6F 72 74 61 6C 07 42 00 00 brary Portal.B..
06B0: 00 0B 00 4D 61 72 61 20 45 73 74 61 74 65 07 43 ...Mara Estate.C
06C0: 00 00 00 21 00 4D 61 6A 27 44 75 6C 20 41 73 74 ...!.Maj'Dul Ast
06D0: 72 6F 6E 6F 6D 65 72 27 73 20 54 6F 77 65 72 20 ronomer's Tower
06E0: 50 6F 72 74 61 6C 07 44 00 00 00 14 00 4D 61 6A Portal.D.....Maj
06F0: 27 44 75 6C 20 53 75 69 74 65 20 50 6F 72 74 61 'Dul Suite Porta
0700: 6C 07 45 00 00 00 17 00 4D 69 73 74 6D 6F 6F 72 l.E.....Mistmoor
0710: 65 20 43 72 61 67 73 20 45 73 74 61 74 65 73 07 e Crags Estates.
0720: 46 00 00 00 0D 00 4F 61 6B 6D 79 73 74 20 47 6C F.....Oakmyst Gl
0730: 61 64 65 07 47 00 00 00 12 00 4F 70 65 72 61 20 ade.G.....Opera
0740: 48 6F 75 73 65 20 50 6F 72 74 61 6C 07 48 00 00 House Portal.H..
0750: 00 16 00 50 65 72 73 6F 6E 61 6C 20 47 72 6F 74 ...Personal Grot
0760: 74 6F 20 50 6F 72 74 61 6C 07 49 00 00 00 17 00 to Portal.I.....
0770: 52 75 6D 20 52 75 6E 6E 65 72 73 20 43 6F 76 65 Rum Runners Cove
0780: 20 50 6F 72 74 61 6C 07 4A 00 00 00 12 00 50 6C Portal.J.....Pl
0790: 61 6E 65 74 61 72 69 75 6D 20 50 6F 72 74 61 6C anetarium Portal
07A0: 07 4B 00 00 00 14 00 52 65 73 65 61 72 63 68 65 .K.....Researche
07B0: 72 27 73 20 53 61 6E 63 74 75 6D 07 4C 00 00 00 r's Sanctum.L...
07C0: 1E 00 52 65 73 69 64 65 6E 63 65 20 6F 66 20 74 ..Residence of t
07D0: 68 65 20 42 6C 61 64 65 73 20 50 6F 72 74 61 6C he Blades Portal
07E0: 07 4D 00 00 00 16 00 53 61 6E 63 74 75 73 20 53 .M.....Sanctus S
07F0: 65 72 75 20 50 72 6F 6D 65 6E 61 64 65 07 4E 00 eru Promenade.N.
0800: 00 00 22 00 53 61 6E 74 61 20 47 6C 75 67 27 73 ..".Santa Glug's
0810: 20 43 68 65 65 72 66 75 6C 20 48 6F 6C 69 64 61 Cheerful Holida
0820: 79 20 48 6F 6D 65 07 4F 00 00 00 17 00 53 65 63 y Home.O.....Sec
0830: 6C 75 64 65 64 20 53 61 6E 63 74 75 6D 20 50 6F luded Sanctum Po
0840: 72 74 61 6C 07 50 00 00 00 18 00 53 6B 79 62 6C rtal.P.....Skybl
0850: 61 64 65 20 53 6B 69 66 66 20 4C 61 75 6E 63 68 ade Skiff Launch
0860: 70 61 64 07 51 00 00 00 0E 00 53 6E 6F 77 79 20 pad.Q.....Snowy
0870: 44 77 65 6C 6C 69 6E 67 07 52 00 00 00 1D 00 53 Dwelling.R.....S
0880: 70 72 6F 63 6B 65 74 27 73 20 49 6E 74 65 72 6C procket's Interl
0890: 6F 63 6B 69 6E 67 20 50 6C 61 6E 65 07 53 00 00 ocking Plane.S..
08A0: 00 17 00 53 74 6F 72 6D 20 54 6F 77 65 72 20 49 ...Storm Tower I
08B0: 73 6C 65 20 50 6F 72 74 61 6C 07 54 00 00 00 21 sle Portal.T...!
08C0: 00 52 65 6C 69 63 20 54 69 6E 6B 65 72 20 50 72 .Relic Tinker Pr
08D0: 65 73 74 69 67 65 20 48 6F 6D 65 20 50 6F 72 74 estige Home Port
08E0: 61 6C 07 55 00 00 00 10 00 54 65 6E 65 62 72 6F al.U.....Tenebro
08F0: 75 73 20 50 6F 72 74 61 6C 07 56 00 00 00 10 00 us Portal.V.....
0900: 54 68 65 20 42 61 75 62 62 6C 65 73 68 69 72 65 The Baubbleshire
0910: 07 57 00 00 00 0F 00 54 69 6E 6B 65 72 65 72 27 .W.....Tinkerer'
0920: 73 20 49 73 6C 65 07 58 00 00 00 12 00 54 6F 77 s Isle.X.....Tow
0930: 65 72 20 6F 66 20 4B 6E 6F 77 6C 65 64 67 65 07 er of Knowledge.
0940: 59 00 00 00 15 00 55 6E 63 61 6E 6E 79 20 45 73 Y.....Uncanny Es
0950: 74 61 74 65 20 50 6F 72 74 61 6C 07 5A 00 00 00 tate Portal.Z...
0960: 1E 00 56 61 63 61 6E 74 20 45 73 74 61 74 65 20 ..Vacant Estate
0970: 6F 66 20 55 6E 72 65 73 74 20 50 6F 72 74 61 6C of Unrest Portal
0980: 07 5B 00 00 00 18 00 56 61 6C 65 20 6F 66 20 48 .[.....Vale of H
0990: 61 6C 66 70 69 6E 74 20 44 65 6C 69 67 68 74 07 alfpint Delight.
09A0: 5C 00 00 00 26 00 4C 69 6F 6E 27 73 20 4D 61 6E \...&.Lion's Man
09B0: 65 20 56 65 73 74 69 67 65 20 52 6F 6F 6D 20 2D e Vestige Room -
09C0: 20 4E 65 74 74 6C 65 76 69 6C 6C 65 07 5D 00 00 Nettleville.]..
09D0: 00 2C 00 4C 69 6F 6E 27 73 20 4D 61 6E 65 20 56 .,.Lion's Mane V
09E0: 65 73 74 69 67 65 20 52 6F 6F 6D 20 2D 20 53 74 estige Room - St
09F0: 61 72 63 72 65 73 74 20 43 6F 6D 6D 75 6E 65 07 arcrest Commune.
0A00: 5E 00 00 00 29 00 4C 69 6F 6E 27 73 20 4D 61 6E ^...).Lion's Man
0A10: 65 20 56 65 73 74 69 67 65 20 52 6F 6F 6D 20 2D e Vestige Room -
0A20: 20 47 72 61 79 73 74 6F 6E 65 20 59 61 72 64 07 Graystone Yard.
0A30: 5F 00 00 00 2C 00 4C 69 6F 6E 27 73 20 4D 61 6E _...,.Lion's Man
0A40: 65 20 56 65 73 74 69 67 65 20 52 6F 6F 6D 20 2D e Vestige Room -
0A50: 20 43 61 73 74 6C 65 76 69 65 77 20 48 61 6D 6C Castleview Haml
0A60: 65 74 07 60 00 00 00 2A 00 4C 69 6F 6E 27 73 20 et.`...*.Lion's
0A70: 4D 61 6E 65 20 56 65 73 74 69 67 65 20 52 6F 6F Mane Vestige Roo
0A80: 6D 20 2D 20 54 68 65 20 57 69 6C 6C 6F 77 20 57 m - The Willow W
0A90: 6F 6F 64 07 61 00 00 00 2B 00 4C 69 6F 6E 27 73 ood.a...+.Lion's
0AA0: 20 4D 61 6E 65 20 56 65 73 74 69 67 65 20 52 6F Mane Vestige Ro
0AB0: 6F 6D 20 2D 20 54 68 65 20 42 61 75 62 62 6C 65 om - The Baubble
0AC0: 73 68 69 72 65 07 62 00 00 00 FF FF FF FF shire.b.......
*/
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,817 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __EQ2_ITEMS__
#define __EQ2_ITEMS__
#include <map>
#include <vector>
#include "../../common/types.hpp"
#include "../../common/data_buffer.hpp"
#include "../../common/misc_functions.hpp"
#include "../Commands/Commands.h"
#include "../../common/config_reader.hpp"
using namespace std;
class MasterItemList;
class Player;
extern MasterItemList master_item_list;
#define EQ2_PRIMARY_SLOT 0
#define EQ2_SECONDARY_SLOT 1
#define EQ2_HEAD_SLOT 2
#define EQ2_CHEST_SLOT 3
#define EQ2_SHOULDERS_SLOT 4
#define EQ2_FOREARMS_SLOT 5
#define EQ2_HANDS_SLOT 6
#define EQ2_LEGS_SLOT 7
#define EQ2_FEET_SLOT 8
#define EQ2_LRING_SLOT 9
#define EQ2_RRING_SLOT 10
#define EQ2_EARS_SLOT_1 11
#define EQ2_EARS_SLOT_2 12
#define EQ2_NECK_SLOT 13
#define EQ2_LWRIST_SLOT 14
#define EQ2_RWRIST_SLOT 15
#define EQ2_RANGE_SLOT 16
#define EQ2_AMMO_SLOT 17
#define EQ2_WAIST_SLOT 18
#define EQ2_CLOAK_SLOT 19
#define EQ2_CHARM_SLOT_1 20
#define EQ2_CHARM_SLOT_2 21
#define EQ2_FOOD_SLOT 22
#define EQ2_DRINK_SLOT 23
#define EQ2_TEXTURES_SLOT 24
#define EQ2_UNKNOWN_SLOT 25
#define PRIMARY_SLOT 1
#define SECONDARY_SLOT 2
#define HEAD_SLOT 4
#define CHEST_SLOT 8
#define SHOULDERS_SLOT 16
#define FOREARMS_SLOT 32
#define HANDS_SLOT 64
#define LEGS_SLOT 128
#define FEET_SLOT 256
#define LRING_SLOT 512
#define RRING_SLOT 1024
#define EARS_SLOT_1 2048
#define EARS_SLOT_2 4096
#define NECK_SLOT 8192
#define LWRIST_SLOT 16384
#define RWRIST_SLOT 32768
#define RANGE_SLOT 65536
#define AMMO_SLOT 131072
#define WAIST_SLOT 262144
#define CLOAK_SLOT 524288
#define CHARM_SLOT_1 1048576
#define CHARM_SLOT_2 2097152
#define FOOD_SLOT 4194304
#define DRINK_SLOT 8388608
#define TEXTURES_SLOT 16777216
#define NUM_BANK_SLOTS 12
#define NUM_SHARED_BANK_SLOTS 8
#define NUM_SLOTS 25
#define NUM_INV_SLOTS 6
#define INV_SLOT1 0
#define INV_SLOT2 50
#define INV_SLOT3 100
#define INV_SLOT4 150
#define INV_SLOT5 200
#define INV_SLOT6 250
#define BANK_SLOT1 1000
#define BANK_SLOT2 1100
#define BANK_SLOT3 1200
#define BANK_SLOT4 1300
#define BANK_SLOT5 1400
#define BANK_SLOT6 1500
#define BANK_SLOT7 1600
#define BANK_SLOT8 1700
#define ATTUNED 1
#define ATTUNEABLE 2
#define ARTIFACT 4
#define LORE 8
#define TEMPORARY 16
#define NO_TRADE 32
#define NO_VALUE 64
#define NO_ZONE 128
#define NO_DESTROY 256
#define CRAFTED 512
#define GOOD_ONLY 1024
#define EVIL_ONLY 2048
#define ITEM_WIELD_TYPE_DUAL 1
#define ITEM_WIELD_TYPE_SINGLE 2
#define ITEM_WIELD_TYPE_TWO_HAND 4
#define ITEM_TYPE_NORMAL 0
#define ITEM_TYPE_WEAPON 1
#define ITEM_TYPE_RANGED 2
#define ITEM_TYPE_ARMOR 3
#define ITEM_TYPE_SHIELD 4
#define ITEM_TYPE_BAG 5
#define ITEM_TYPE_SKILL 6
#define ITEM_TYPE_RECIPE 7
#define ITEM_TYPE_FOOD 8
#define ITEM_TYPE_BAUBLE 9
#define ITEM_TYPE_HOUSE 10
#define ITEM_TYPE_THROWN 11
#define ITEM_TYPE_HOUSE_CONTAINER 12
#define ITEM_TYPE_BOOK 13
#define ITEM_TYPE_ADORNMENT 14
#define ITEM_TYPE_PATTERN 15
#define ITEM_TYPE_ARMORSET 16
#define ITEM_MENU_TYPE_GENERIC 1
#define ITEM_MENU_TYPE_EQUIP 2
#define ITEM_MENU_TYPE_BAG 4
#define ITEM_MENU_TYPE_HOUSE 8
#define ITEM_MENU_TYPE_SCRIBE 32
#define ITEM_MENU_TYPE_INVALID 128
#define ITEM_MENU_TYPE_BROKEN 512
#define ITEM_MENU_TYPE_ATTUNED 2048
#define ITEM_MENU_TYPE_ATTUNEABLE 4096
#define ITEM_MENU_TYPE_BOOK 8192
#define ITEM_MENU_TYPE_DISPLAY_CHARGES 16384
#define ITEM_MENU_TYPE_NAMEPET 65536
#define ITEM_MENU_TYPE_USE 524288
#define ITEM_MENU_TYPE_DRINK 8388608
#define ITEM_MENU_TYPE_REDEEM 536870912
#define ITEM_TAG_UNCOMMON 3 //tier tags
#define ITEM_TAG_TREASURED 4
#define ITEM_TAG_LEGENDARY 7
#define ITEM_TAG_FABLED 9
#define ITEM_TAG_MYTHICAL 12
#define ITEM_BROKER_TYPE_ANY 0xFFFFFFFF
#define ITEM_BROKER_TYPE_ADORNMENT 134217728
#define ITEM_BROKER_TYPE_AMMO 1024
#define ITEM_BROKER_TYPE_ATTUNEABLE 16384
#define ITEM_BROKER_TYPE_BAG 2048
#define ITEM_BROKER_TYPE_BAUBLE 16777216
#define ITEM_BROKER_TYPE_BOOK 128
#define ITEM_BROKER_TYPE_CHAINARMOR 2097152
#define ITEM_BROKER_TYPE_CLOAK 1073741824
#define ITEM_BROKER_TYPE_CLOTHARMOR 524288
#define ITEM_BROKER_TYPE_COLLECTABLE 67108864
#define ITEM_BROKER_TYPE_CRUSHWEAPON 4
#define ITEM_BROKER_TYPE_DRINK 131072
#define ITEM_BROKER_TYPE_FOOD 4096
#define ITEM_BROKER_TYPE_HOUSEITEM 512
#define ITEM_BROKER_TYPE_JEWELRY 262144
#define ITEM_BROKER_TYPE_LEATHERARMOR 1048576
#define ITEM_BROKER_TYPE_LORE 8192
#define ITEM_BROKER_TYPE_MISC 1
#define ITEM_BROKER_TYPE_PIERCEWEAPON 8
#define ITEM_BROKER_TYPE_PLATEARMOR 4194304
#define ITEM_BROKER_TYPE_POISON 65536
#define ITEM_BROKER_TYPE_POTION 32768
#define ITEM_BROKER_TYPE_RECIPEBOOK 8388608
#define ITEM_BROKER_TYPE_SALESDISPLAY 33554432
#define ITEM_BROKER_TYPE_SHIELD 32
#define ITEM_BROKER_TYPE_SLASHWEAPON 2
#define ITEM_BROKER_TYPE_SPELLSCROLL 64
#define ITEM_BROKER_TYPE_TINKERED 268435456
#define ITEM_BROKER_TYPE_TRADESKILL 256
#define ITEM_BROKER_SLOT_ANY 0xFFFFFFFF
#define ITEM_BROKER_SLOT_AMMO 65536
#define ITEM_BROKER_SLOT_CHARM 524288
#define ITEM_BROKER_SLOT_CHEST 32
#define ITEM_BROKER_SLOT_CLOAK 262144
#define ITEM_BROKER_SLOT_DRINK 2097152
#define ITEM_BROKER_SLOT_EARS 4096
#define ITEM_BROKER_SLOT_FEET 1024
#define ITEM_BROKER_SLOT_FOOD 1048576
#define ITEM_BROKER_SLOT_FOREARMS 128
#define ITEM_BROKER_SLOT_HANDS 256
#define ITEM_BROKER_SLOT_HEAD 16
#define ITEM_BROKER_SLOT_LEGS 512
#define ITEM_BROKER_SLOT_NECK 8192
#define ITEM_BROKER_SLOT_PRIMARY 1
#define ITEM_BROKER_SLOT_PRIMARY_2H 2
#define ITEM_BROKER_SLOT_RANGE_WEAPON 32768
#define ITEM_BROKER_SLOT_RING 2048
#define ITEM_BROKER_SLOT_SECONDARY 8
#define ITEM_BROKER_SLOT_SHOULDERS 64
#define ITEM_BROKER_SLOT_WAIST 131072
#define ITEM_BROKER_SLOT_WRIST 16384
#define ITEM_BROKER_STAT_TYPE_NONE 0
#define ITEM_BROKER_STAT_TYPE_DEF 2
#define ITEM_BROKER_STAT_TYPE_STR 4
#define ITEM_BROKER_STAT_TYPE_STA 8
#define ITEM_BROKER_STAT_TYPE_AGI 16
#define ITEM_BROKER_STAT_TYPE_WIS 32
#define ITEM_BROKER_STAT_TYPE_INT 64
#define ITEM_BROKER_STAT_TYPE_HEALTH 128
#define ITEM_BROKER_STAT_TYPE_POWER 256
#define ITEM_BROKER_STAT_TYPE_HEAT 512
#define ITEM_BROKER_STAT_TYPE_COLD 1024
#define ITEM_BROKER_STAT_TYPE_MAGIC 2048
#define ITEM_BROKER_STAT_TYPE_MENTAL 4096
#define ITEM_BROKER_STAT_TYPE_DIVINE 8192
#define ITEM_BROKER_STAT_TYPE_POISON 16384
#define ITEM_BROKER_STAT_TYPE_DISEASE 32768
#define ITEM_BROKER_STAT_TYPE_CRUSH 65536
#define ITEM_BROKER_STAT_TYPE_SLASH 131072
#define ITEM_BROKER_STAT_TYPE_PIERCE 262144
#define ITEM_BROKER_STAT_TYPE_CRITICAL 524288
#define ITEM_BROKER_STAT_TYPE_DBL_ATTACK 1048576
#define ITEM_BROKER_STAT_TYPE_ABILITY_MOD 2097152
#define ITEM_BROKER_STAT_TYPE_POTENCY 4194304
#define OVERFLOW_SLOT 0xFFFFFFFE
#define SLOT_INVALID 0xFFFF
#define ITEM_STAT_STR 0
#define ITEM_STAT_STA 1
#define ITEM_STAT_AGI 2
#define ITEM_STAT_WIS 3
#define ITEM_STAT_INT 4
#define ITEM_STAT_ADORNING 100
#define ITEM_STAT_AGGRESSION 101
#define ITEM_STAT_ARTIFICING 102
#define ITEM_STAT_ARTISTRY 103
#define ITEM_STAT_CHEMISTRY 104
#define ITEM_STAT_CRUSHING 105
#define ITEM_STAT_DEFENSE 106
#define ITEM_STAT_DEFLECTION 107
#define ITEM_STAT_DISRUPTION 108
#define ITEM_STAT_FISHING 109
#define ITEM_STAT_FLETCHING 110
#define ITEM_STAT_FOCUS 111
#define ITEM_STAT_FORESTING 112
#define ITEM_STAT_GATHERING 113
#define ITEM_STAT_METAL_SHAPING 114
#define ITEM_STAT_METALWORKING 115
#define ITEM_STAT_MINING 116
#define ITEM_STAT_MINISTRATION 117
#define ITEM_STAT_ORDINATION 118
#define ITEM_STAT_PARRY 119
#define ITEM_STAT_PIERCING 120
#define ITEM_STAT_RANGED 121
#define ITEM_STAT_SAFE_FALL 122
#define ITEM_STAT_SCRIBING 123
#define ITEM_STAT_SCULPTING 124
#define ITEM_STAT_SLASHING 125
#define ITEM_STAT_SUBJUGATION 126
#define ITEM_STAT_SWIMMING 127
#define ITEM_STAT_TAILORING 128
#define ITEM_STAT_TINKERING 129
#define ITEM_STAT_TRANSMUTING 130
#define ITEM_STAT_TRAPPING 131
#define ITEM_STAT_WEAPON_SKILLS 132
#define ITEM_STAT_VS_PHYSICAL 200
#define ITEM_STAT_VS_ELEMENTAL 201
#define ITEM_STAT_VS_NOXIOUS 202
#define ITEM_STAT_VS_ARCANE 203
//#define ITEM_STAT_VS_SLASH 200
//#define ITEM_STAT_VS_CRUSH 201
//#define ITEM_STAT_VS_PIERCE 202
//#define ITEM_STAT_VS_HEAT 203
//#define ITEM_STAT_VS_COLD 204
//#define ITEM_STAT_VS_MAGIC 205
//#define ITEM_STAT_VS_MENTAL 206
//#define ITEM_STAT_VS_DIVINE 207
//#define ITEM_STAT_VS_DISEASE 208
//#define ITEM_STAT_VS_POISON 209
//#define ITEM_STAT_VS_DROWNING 210
//#define ITEM_STAT_VS_FALLING 211
//#define ITEM_STAT_VS_PAIN 212
//#define ITEM_STAT_VS_MELEE 213
#define ITEM_STAT_DMG_SLASH 300
#define ITEM_STAT_DMG_CRUSH 301
#define ITEM_STAT_DMG_PIERCE 302
#define ITEM_STAT_DMG_HEAT 303
#define ITEM_STAT_DMG_COLD 304
#define ITEM_STAT_DMG_MAGIC 305
#define ITEM_STAT_DMG_MENTAL 306
#define ITEM_STAT_DMG_DIVINE 307
#define ITEM_STAT_DMG_DISEASE 308
#define ITEM_STAT_DMG_POISON 309
#define ITEM_STAT_DMG_DROWNING 310
#define ITEM_STAT_DMG_FALLING 311
#define ITEM_STAT_DMG_PAIN 312
#define ITEM_STAT_DMG_MELEE 313
#define ITEM_STAT_HEALTH 500
#define ITEM_STAT_POWER 501
#define ITEM_STAT_CONCENTRATION 502
#define ITEM_STAT_SAVAGERY 503
#define ITEM_STAT_HPREGEN 600
#define ITEM_STAT_MANAREGEN 601
#define ITEM_STAT_HPREGENPPT 602
#define ITEM_STAT_MPREGENPPT 603
#define ITEM_STAT_COMBATHPREGENPPT 604
#define ITEM_STAT_COMBATMPREGENPPT 605
#define ITEM_STAT_MAXHP 606
#define ITEM_STAT_MAXHPPERC 607
#define ITEM_STAT_MAXHPPERCFINAL 608
#define ITEM_STAT_SPEED 609
#define ITEM_STAT_SLOW 610
#define ITEM_STAT_MOUNTSPEED 611
#define ITEM_STAT_MOUNTAIRSPEED 612
#define ITEM_STAT_LEAPSPEED 613
#define ITEM_STAT_LEAPTIME 614
#define ITEM_STAT_GLIDEEFFICIENCY 615
#define ITEM_STAT_OFFENSIVESPEED 616
#define ITEM_STAT_ATTACKSPEED 617
#define ITEM_STAT_SPELLWEAPONATTACKSPEED 618
#define ITEM_STAT_MAXMANA 619
#define ITEM_STAT_MAXMANAPERC 620
#define ITEM_STAT_MAXATTPERC 621
#define ITEM_STAT_BLURVISION 622
#define ITEM_STAT_MAGICLEVELIMMUNITY 623
#define ITEM_STAT_HATEGAINMOD 624
#define ITEM_STAT_COMBATEXPMOD 625
#define ITEM_STAT_TRADESKILLEXPMOD 626
#define ITEM_STAT_ACHIEVEMENTEXPMOD 627
#define ITEM_STAT_SIZEMOD 628
#define ITEM_STAT_DPS 629
#define ITEM_STAT_SPELLWEAPONDPS 630
#define ITEM_STAT_STEALTH 631
#define ITEM_STAT_INVIS 632
#define ITEM_STAT_SEESTEALTH 633
#define ITEM_STAT_SEEINVIS 634
#define ITEM_STAT_EFFECTIVELEVELMOD 635
#define ITEM_STAT_RIPOSTECHANCE 636
#define ITEM_STAT_PARRYCHANCE 637
#define ITEM_STAT_DODGECHANCE 638
#define ITEM_STAT_AEAUTOATTACKCHANCE 639
#define ITEM_STAT_SPELLWEAPONAEAUTOATTACKCHANCE 640
#define ITEM_STAT_DOUBLEATTACKCHANCE 641
#define ITEM_STAT_PVPDOUBLEATTACKCHANCE 642
#define ITEM_STAT_SPELLWEAPONAEAUTOATTACKCHANCE 643
#define ITEM_STAT_PVPSPELLWEAPONDOUBLEATTACKCHANCE 644
#define ITEM_STAT_SPELLDOUBLEATTACKCHANCE 645
#define ITEM_STAT_PVPSPELLDOUBLEATTACKCHANCE 646
#define ITEM_STAT_FLURRY 647
#define ITEM_STAT_SPELLWEAPONFLURRY 648
#define ITEM_STAT_MELEEDAMAGEMULTIPLIER 649
#define ITEM_STAT_EXTRAHARVESTCHANCE 650
#define ITEM_STAT_EXTRASHIELDBLOCKCHANCE 651
#define ITEM_STAT_ITEMHPREGENPPT 652
#define ITEM_STAT_ITEMPPREGENPPT 653
#define ITEM_STAT_MELEECRITCHANCE 654
#define ITEM_STAT_CRITAVOIDANCE 655
#define ITEM_STAT_BENEFICIALCRITCHANCE 656
#define ITEM_STAT_CRITBONUS 657
#define ITEM_STAT_PVPCRITBONUS 658
#define ITEM_STAT_BASEMODIFIER 659
#define ITEM_STAT_PVPBASEMODIFIER 660
#define ITEM_STAT_UNCONSCIOUSHPMOD 661
#define ITEM_STAT_SPELLTIMEREUSEPCT 662
#define ITEM_STAT_SPELLTIMERECOVERYPCT 663
#define ITEM_STAT_SPELLTIMECASTPCT 664
#define ITEM_STAT_SPELLTIMEREUSESPELLONLY 665
#define ITEM_STAT_MELEEWEAPONRANGE 666
#define ITEM_STAT_RANGEDWEAPONRANGE 667
#define ITEM_STAT_FALLINGDAMAGEREDUCTION 668
#define ITEM_STAT_RIPOSTEDAMAGE 669
#define ITEM_STAT_MINIMUMDEFLECTIONCHANCE 670
#define ITEM_STAT_MOVEMENTWEAVE 671
#define ITEM_STAT_COMBATHPREGEN 672
#define ITEM_STAT_COMBATMANAREGEN 673
#define ITEM_STAT_CONTESTSPEEDBOOST 674
#define ITEM_STAT_TRACKINGAVOIDANCE 675
#define ITEM_STAT_STEALTHINVISSPEEDMOD 676
#define ITEM_STAT_LOOT_COIN 677
#define ITEM_STAT_ARMORMITIGATIONINCREASE 678
#define ITEM_STAT_AMMOCONSERVATION 679
#define ITEM_STAT_STRIKETHROUGH 680
#define ITEM_STAT_STATUSBONUS 681
#define ITEM_STAT_ACCURACY 682
#define ITEM_STAT_COUNTERSTRIKE 683
#define ITEM_STAT_SHIELDBASH 684
#define ITEM_STAT_WEAPONDAMAGEBONUS 685
#define ITEM_STAT_WEAPONDAMAGEBONUSMELEEONLY 686
#define ITEM_STAT_ADDITIONALRIPOSTECHANCE 687
#define ITEM_STAT_CRITICALMITIGATION 688
#define ITEM_STAT_PVPTOUGHNESS 689
#define ITEM_STAT_PVPLETHALITY 690
#define ITEM_STAT_STAMINABONUS 691
#define ITEM_STAT_WISDOMMITBONUS 692
#define ITEM_STAT_HEALRECEIVE 693
#define ITEM_STAT_HEALRECEIVEPERC 694
#define ITEM_STAT_PVPCRITICALMITIGATION 695
#define ITEM_STAT_BASEAVOIDANCEBONUS 696
#define ITEM_STAT_INCOMBATSAVAGERYREGEN 697
#define ITEM_STAT_OUTOFCOMBATSAVAGERYREGEN 698
#define ITEM_STAT_SAVAGERYREGEN 699
#define ITEM_STAT_SAVAGERYGAINMOD 6100
#define ITEM_STAT_MAXSAVAGERYLEVEL 6101
#define ITEM_STAT_SPELL_DAMAGE 700
#define ITEM_STAT_HEAL_AMOUNT 701
#define ITEM_STAT_SPELL_AND_HEAL 702
#define ITEM_STAT_COMBAT_ART_DAMAGE 703
#define ITEM_STAT_SPELL_AND_COMBAT_ART_DAMAGE 704
#define ITEM_STAT_TAUNT_AMOUNT 705
#define ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE 706
#define ITEM_STAT_ABILITY_MODIFIER 707
#pragma pack(1)
struct ItemStatsValues{
sint16 str;
sint16 sta;
sint16 agi;
sint16 wis;
sint16 int_;
sint16 vs_slash;
sint16 vs_crush;
sint16 vs_pierce;
sint16 vs_heat;
sint16 vs_cold;
sint16 vs_magic;
sint16 vs_mental;
sint16 vs_divine;
sint16 vs_disease;
sint16 vs_poison;
sint16 health;
sint16 power;
sint8 concentration;
};
struct ItemCore{
int32 item_id;
sint32 soe_id;
int32 bag_id;
sint32 inv_slot_id;
sint16 slot_id;
int8 index;
int16 icon;
int16 count;
int8 tier;
int8 num_slots;
int32 unique_id;
int8 num_free_slots;
int16 recommended_level;
bool item_locked;
};
#pragma pack()
struct ItemStat{
string stat_name;
int8 stat_type;
sint16 stat_subtype;
int16 stat_type_combined;
float value;
};
struct ItemLevelOverride{
int8 adventure_class;
int8 tradeskill_class;
int16 level;
};
struct ItemClass{
int8 adventure_class;
int8 tradeskill_class;
int16 level;
};
struct ItemAppearance{
int16 type;
int8 red;
int8 green;
int8 blue;
int8 highlight_red;
int8 highlight_green;
int8 highlight_blue;
};
class PlayerItemList;
class Item{
public:
#pragma pack(1)
struct ItemStatString{
EQ2_8BitString stat_string;
};
struct Generic_Info{
int8 show_name;
int8 creator_flag;
int16 item_flags;
int8 condition;
int32 weight; // num/10
int32 skill_req1;
int32 skill_req2;
int16 skill_min;
int8 item_type; //0=normal, 1=weapon, 2=range, 3=armor, 4=shield, 5=container, 6=spell scroll, 7=recipe book, 8=food/drink, 9=bauble, 10=house item, 11=ammo, 12=house container
int16 appearance_id;
int8 appearance_red;
int8 appearance_green;
int8 appearance_blue;
int8 appearance_highlight_red;
int8 appearance_highlight_green;
int8 appearance_highlight_blue;
int8 collectable;
int32 offers_quest_id;
int32 part_of_quest_id;
int16 max_charges;
int8 display_charges;
int64 adventure_classes;
int64 tradeskill_classes;
int16 adventure_default_level;
int16 tradeskill_default_level;
int8 usable;
};
struct Armor_Info {
int16 mitigation_low;
int16 mitigation_high;
};
struct Weapon_Info {
int16 wield_type;
int16 damage_low1;
int16 damage_high1;
int16 damage_low2;
int16 damage_high2;
int16 damage_low3;
int16 damage_high3;
int16 delay;
float rating;
};
struct Shield_Info {
Armor_Info armor_info;
};
struct Ranged_Info {
Weapon_Info weapon_info;
int16 range_low;
int16 range_high;
};
struct Bag_Info {
int8 num_slots;
int16 weight_reduction;
};
struct Food_Info{
int8 type; //0=water, 1=food
int8 level;
float duration;
int8 satiation;
};
struct Bauble_Info{
int16 cast;
int16 recovery;
int32 duration;
float recast;
int8 display_slot_optional;
int8 display_cast_time;
int8 display_bauble_type;
float effect_radius;
int32 max_aoe_targets;
int8 display_until_cancelled;
};
struct Book_Info{
int8 language;
EQ2_16BitString author;
EQ2_16BitString title;
};
struct Skill_Info{
int32 spell_id;
int32 spell_tier;
};
struct House_Info{
int32 status_rent_reduction;
};
struct HouseContainer_Info{
int16 disallowed_types;
int16 allowed_types;
int8 num_slots;
};
struct RecipeBook_Info{
vector<string> recipes;
int8 uses;
};
struct Thrown_Info{
sint32 range;
sint32 damage_modifier;
float hit_bonus;
int32 damage_type;
};
struct ItemEffect{
EQ2_16BitString effect;
int8 percentage;
int8 subbulletflag;
};
#pragma pack()
Item();
Item(Item* in_item);
~Item();
string lowername;
string name;
string description;
int8 stack_count;
int32 sell_price;
int32 max_sell_value;
bool save_needed;
int8 weapon_type;
string adornment;
string creator;
vector<ItemStat*> item_stats;
vector<ItemStatString*> item_string_stats;
vector<ItemLevelOverride*> item_level_overrides;
vector<ItemEffect*> item_effects;
Generic_Info generic_info;
Weapon_Info* weapon_info;
Ranged_Info* ranged_info;
Armor_Info* armor_info;
Bag_Info* bag_info;
Food_Info* food_info;
Bauble_Info* bauble_info;
Book_Info* book_info;
Skill_Info* skill_info;
RecipeBook_Info* recipebook_info;
Thrown_Info* thrown_info;
vector<int8> slot_data;
ItemCore details;
int32 spell_id;
int8 spell_tier;
string item_script;
void AddEffect(string effect, int8 percentage, int8 subbulletflag);
int32 GetMaxSellValue();
void SetMaxSellValue(int32 val);
void SetItem(Item* old_item);
int16 GetOverrideLevel(int8 adventure_class, int8 tradeskill_class);
void AddLevelOverride(int8 adventure_class, int8 tradeskill_class, int16 level);
void AddLevelOverride(ItemLevelOverride* class_);
bool CheckClassLevel(int8 adventure_class, int8 tradeskill_class, int16 level);
bool CheckClass(int8 adventure_class, int8 tradeskill_class);
bool CheckLevel(int8 adventure_class, int8 tradeskill_class, int16 level);
void SetAppearance(int16 type, int8 red, int8 green, int8 blue, int8 highlight_red, int8 highlight_green, int8 highlight_blue);
void SetAppearance(ItemAppearance* appearance);
void AddStat(ItemStat* in_stat);
void AddStatString(ItemStatString* in_stat);
void AddStat(int8 type, int16 subtype, float value, char* name = 0);
void SetWeaponType(int8 type);
int8 GetWeaponType();
bool HasSlot(int8 slot, int8 slot2 = 255);
bool IsNormal();
bool IsWeapon();
bool IsArmor();
bool IsRanged();
bool IsBag();
bool IsFood();
bool IsBauble();
bool IsSkill();
bool IsHouseItem();
bool IsHouseContainer();
bool IsShield();
bool IsAdornment();
bool IsAmmo();
bool IsBook();
bool IsChainArmor();
bool IsClothArmor();
bool IsCollectable();
bool IsCloak();
bool IsCrushWeapon();
bool IsFoodFood();
bool IsFoodDrink();
bool IsJewelry();
bool IsLeatherArmor();
bool IsMisc();
bool IsPierceWeapon();
bool IsPlateArmor();
bool IsPoison();
bool IsPotion();
bool IsRecipeBook();
bool IsSalesDisplay();
bool IsSlashWeapon();
bool IsSpellScroll();
bool IsTinkered();
bool IsTradeskill();
bool IsThrown();
void SetItemScript(string name);
const char* GetItemScript();
int32 CalculateRepairCost();
void SetItemType(int8 in_type);
void serialize(PacketStruct* packet, bool show_name = false, Player* player = 0, int16 packet_type = 0, int8 subtype = 0, bool loot_item = false);
EQ2Packet* serialize(int16 version, bool show_name = false, Player* player = 0, bool include_twice = true, int16 packet_type = 0, int8 subtype = 0, bool merchant_item = false, bool loot_item = false);
PacketStruct* PrepareItem(int16 version, bool merchant_item = false, bool loot_item = false);
bool CheckFlag(int32 flag);
void AddSlot(int8 slot_id);
void SetSlots(int32 slots);
bool needs_deletion;
};
class MasterItemList{
public:
~MasterItemList();
map<int32,Item*> items;
Item* GetItem(int32 id);
Item* GetItemByName(const char *name);
ItemStatsValues* CalculateItemBonuses(int32 item_id);
ItemStatsValues* CalculateItemBonuses(Item* desc, ItemStatsValues* values = 0);
vector<Item*>* GetItems(string name, int32 itype, int32 ltype, int32 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass);
vector<Item*>* GetItems(map<string, string> criteria);
void AddItem(Item* item);
bool IsBag(int32 item_id);
void RemoveAll();
static int32 NextUniqueID();
static void ResetUniqueID(int32 new_id);
static int32 next_unique_id;
};
class PlayerItemList {
public:
PlayerItemList();
~PlayerItemList();
// int16 number;
map<int32, Item*> indexed_items;
map<sint32, map<int16, Item*> > items;
// map< int8, Item* > inv_items;
// map< int8, Item* > bank_items;
bool SharedBankAddAllowed(Item* item);
vector<Item*>* GetItemsFromBagID(sint32 bag_id);
vector<Item*>* GetItemsInBag(Item* bag);
Item* GetBag(int8 inventory_slot, bool lock = true);
bool HasItem(int32 id, bool include_bank = false);
Item* GetItemFromIndex(int32 index);
void MoveItem(Item* item, sint32 inv_slot, int16 slot, bool erase_old = true);
bool MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 charges);
Item* GetItemFromUniqueID(int32 item_id, bool include_bank = false, bool lock = true);
Item* GetItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true);
bool AssignItemToFreeSlot(Item* item);
int16 GetNumberOfFreeSlots();
int16 GetNumberOfItems();
bool HasFreeSlot();
bool HasFreeBagSlot();
void DestroyItem(int16 index);
Item* CanStack(Item* item, bool include_bank = false);
void RemoveItem(Item* item, bool delete_item = false);
void AddItem(Item* item);
Item* GetItem(sint32 bag_slot, int16 slot);
EQ2Packet* serialize(Player* player, int16 version);
uchar* xor_packet;
uchar* orig_packet;
map<int32, Item*>* GetAllItems();
bool HasFreeBankSlot();
int8 FindFreeBankSlot();
private:
void Stack(Item* orig_item, Item* item);
Mutex MPlayerItems;
int16 packet_count;
};
class OverFlowItemList : public PlayerItemList {
public:
bool OverFlowSlotFull();
int8 GetNextOverFlowSlot();
bool AddItem(Item* item);
Item* GetOverFlowItem();
};
class EquipmentItemList{
public:
EquipmentItemList();
EquipmentItemList(const EquipmentItemList& list);
~EquipmentItemList();
Item* items[NUM_SLOTS];
vector<Item*>* GetAllEquippedItems();
bool HasItem(int32 id);
int8 GetNumberOfItems();
Item* GetItemFromUniqueID(int32 item_id);
Item* GetItemFromItemID(int32 item_id);
void SetItem(int8 slot_id, Item* item);
void RemoveItem(int8 slot, bool delete_item = false);
Item* GetItem(int8 slot_id);
bool AddItem(int8 slot, Item* item);
bool CheckEquipSlot(Item* tmp, int8 slot);
bool CanItemBeEquippedInSlot(Item* tmp, int8 slot);
int8 GetFreeSlot(Item* tmp, int8 slot_id = 255);
ItemStatsValues* CalculateEquipmentBonuses();
EQ2Packet* serialize(int16 version);
uchar* xor_packet;
uchar* orig_packet;
private:
Mutex MEquipmentItems;
};
#endif

View File

@ -0,0 +1,916 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __EQ2_ITEMS__
#define __EQ2_ITEMS__
#include <map>
#include <vector>
#include "../../common/types.hpp"
#include "../../common/data_buffer.hpp"
#include "../../common/misc_functions.hpp"
#include "../Commands/Commands.h"
#include "../../common/config_reader.hpp"
using namespace std;
class MasterItemList;
class Player;
class Entity;
extern MasterItemList master_item_list;
#define EQ2_PRIMARY_SLOT 0
#define EQ2_SECONDARY_SLOT 1
#define EQ2_HEAD_SLOT 2
#define EQ2_CHEST_SLOT 3
#define EQ2_SHOULDERS_SLOT 4
#define EQ2_FOREARMS_SLOT 5
#define EQ2_HANDS_SLOT 6
#define EQ2_LEGS_SLOT 7
#define EQ2_FEET_SLOT 8
#define EQ2_LRING_SLOT 9
#define EQ2_RRING_SLOT 10
#define EQ2_EARS_SLOT_1 11
#define EQ2_EARS_SLOT_2 12
#define EQ2_NECK_SLOT 13
#define EQ2_LWRIST_SLOT 14
#define EQ2_RWRIST_SLOT 15
#define EQ2_RANGE_SLOT 16
#define EQ2_AMMO_SLOT 17
#define EQ2_WAIST_SLOT 18
#define EQ2_CLOAK_SLOT 19
#define EQ2_CHARM_SLOT_1 20
#define EQ2_CHARM_SLOT_2 21
#define EQ2_FOOD_SLOT 22
#define EQ2_DRINK_SLOT 23
#define EQ2_TEXTURES_SLOT 24
#define EQ2_HAIR_SLOT 25
#define EQ2_BEARD_SLOT 26
#define EQ2_WINGS_SLOT 27
#define EQ2_NAKED_CHEST_SLOT 28
#define EQ2_NAKED_LEGS_SLOT 29
#define EQ2_BACK_SLOT 30
#define PRIMARY_SLOT 1
#define SECONDARY_SLOT 2
#define HEAD_SLOT 4
#define CHEST_SLOT 8
#define SHOULDERS_SLOT 16
#define FOREARMS_SLOT 32
#define HANDS_SLOT 64
#define LEGS_SLOT 128
#define FEET_SLOT 256
#define LRING_SLOT 512
#define RRING_SLOT 1024
#define EARS_SLOT_1 2048
#define EARS_SLOT_2 4096
#define NECK_SLOT 8192
#define LWRIST_SLOT 16384
#define RWRIST_SLOT 32768
#define RANGE_SLOT 65536
#define AMMO_SLOT 131072
#define WAIST_SLOT 262144
#define CLOAK_SLOT 524288
#define CHARM_SLOT_1 1048576
#define CHARM_SLOT_2 2097152
#define FOOD_SLOT 4194304
#define DRINK_SLOT 8388608
#define TEXTURES_SLOT 16777216
#define HAIR_SLOT 33554432
#define BEARD_SLOT 67108864
#define WINGS_SLOT 134217728
#define NAKED_CHEST_SLOT 268435456
#define NAKED_LEGS_SLOT 536870912
#define BACK_SLOT 1073741824
#define NUM_BANK_SLOTS 12
#define NUM_SHARED_BANK_SLOTS 8
#define NUM_SLOTS 25
#define NUM_INV_SLOTS 6
#define INV_SLOT1 0
#define INV_SLOT2 50
#define INV_SLOT3 100
#define INV_SLOT4 150
#define INV_SLOT5 200
#define INV_SLOT6 250
#define BANK_SLOT1 1000
#define BANK_SLOT2 1100
#define BANK_SLOT3 1200
#define BANK_SLOT4 1300
#define BANK_SLOT5 1400
#define BANK_SLOT6 1500
#define BANK_SLOT7 1600
#define BANK_SLOT8 1700
#define ATTUNED 1
#define ATTUNEABLE 2
#define ARTIFACT 4
#define LORE 8
#define TEMPORARY 16
#define NO_TRADE 32
#define NO_VALUE 64
#define NO_ZONE 128
#define NO_DESTROY 256
#define CRAFTED 512
#define GOOD_ONLY 1024
#define EVIL_ONLY 2048
#define ITEM_WIELD_TYPE_DUAL 1
#define ITEM_WIELD_TYPE_SINGLE 2
#define ITEM_WIELD_TYPE_TWO_HAND 4
#define ITEM_TYPE_NORMAL 0
#define ITEM_TYPE_WEAPON 1
#define ITEM_TYPE_RANGED 2
#define ITEM_TYPE_ARMOR 3
#define ITEM_TYPE_SHIELD 4
#define ITEM_TYPE_BAG 5
#define ITEM_TYPE_SKILL 6
#define ITEM_TYPE_RECIPE 7
#define ITEM_TYPE_FOOD 8
#define ITEM_TYPE_BAUBLE 9
#define ITEM_TYPE_HOUSE 10
#define ITEM_TYPE_THROWN 11
#define ITEM_TYPE_HOUSE_CONTAINER 12
#define ITEM_TYPE_BOOK 13
#define ITEM_TYPE_ADORNMENT 14
#define ITEM_TYPE_PATTERN 15
#define ITEM_TYPE_ARMORSET 16
#define ITEM_MENU_TYPE_GENERIC 1
#define ITEM_MENU_TYPE_EQUIP 2
#define ITEM_MENU_TYPE_BAG 4
#define ITEM_MENU_TYPE_HOUSE 8
#define ITEM_MENU_TYPE_SCRIBE 32
#define ITEM_MENU_TYPE_INVALID 128
#define ITEM_MENU_TYPE_BROKEN 512
#define ITEM_MENU_TYPE_ATTUNED 2048
#define ITEM_MENU_TYPE_ATTUNEABLE 4096
#define ITEM_MENU_TYPE_BOOK 8192
#define ITEM_MENU_TYPE_DISPLAY_CHARGES 16384
#define ITEM_MENU_TYPE_NAMEPET 65536
#define ITEM_MENU_TYPE_CONSUME 262144
#define ITEM_MENU_TYPE_USE 524288
#define ITEM_MENU_TYPE_DRINK 8388608
#define ITEM_MENU_TYPE_REDEEM 536870912
#define ITEM_TAG_UNCOMMON 3 //tier tags
#define ITEM_TAG_TREASURED 4
#define ITEM_TAG_LEGENDARY 7
#define ITEM_TAG_FABLED 9
#define ITEM_TAG_MYTHICAL 12
#define ITEM_BROKER_TYPE_ANY 0xFFFFFFFF
#define ITEM_BROKER_TYPE_ADORNMENT 134217728
#define ITEM_BROKER_TYPE_AMMO 1024
#define ITEM_BROKER_TYPE_ATTUNEABLE 16384
#define ITEM_BROKER_TYPE_BAG 2048
#define ITEM_BROKER_TYPE_BAUBLE 16777216
#define ITEM_BROKER_TYPE_BOOK 128
#define ITEM_BROKER_TYPE_CHAINARMOR 2097152
#define ITEM_BROKER_TYPE_CLOAK 1073741824
#define ITEM_BROKER_TYPE_CLOTHARMOR 524288
#define ITEM_BROKER_TYPE_COLLECTABLE 67108864
#define ITEM_BROKER_TYPE_CRUSHWEAPON 4
#define ITEM_BROKER_TYPE_DRINK 131072
#define ITEM_BROKER_TYPE_FOOD 4096
#define ITEM_BROKER_TYPE_HOUSEITEM 512
#define ITEM_BROKER_TYPE_JEWELRY 262144
#define ITEM_BROKER_TYPE_LEATHERARMOR 1048576
#define ITEM_BROKER_TYPE_LORE 8192
#define ITEM_BROKER_TYPE_MISC 1
#define ITEM_BROKER_TYPE_PIERCEWEAPON 8
#define ITEM_BROKER_TYPE_PLATEARMOR 4194304
#define ITEM_BROKER_TYPE_POISON 65536
#define ITEM_BROKER_TYPE_POTION 32768
#define ITEM_BROKER_TYPE_RECIPEBOOK 8388608
#define ITEM_BROKER_TYPE_SALESDISPLAY 33554432
#define ITEM_BROKER_TYPE_SHIELD 32
#define ITEM_BROKER_TYPE_SLASHWEAPON 2
#define ITEM_BROKER_TYPE_SPELLSCROLL 64
#define ITEM_BROKER_TYPE_TINKERED 268435456
#define ITEM_BROKER_TYPE_TRADESKILL 256
#define ITEM_BROKER_SLOT_ANY 0xFFFFFFFF
#define ITEM_BROKER_SLOT_AMMO 65536
#define ITEM_BROKER_SLOT_CHARM 524288
#define ITEM_BROKER_SLOT_CHEST 32
#define ITEM_BROKER_SLOT_CLOAK 262144
#define ITEM_BROKER_SLOT_DRINK 2097152
#define ITEM_BROKER_SLOT_EARS 4096
#define ITEM_BROKER_SLOT_FEET 1024
#define ITEM_BROKER_SLOT_FOOD 1048576
#define ITEM_BROKER_SLOT_FOREARMS 128
#define ITEM_BROKER_SLOT_HANDS 256
#define ITEM_BROKER_SLOT_HEAD 16
#define ITEM_BROKER_SLOT_LEGS 512
#define ITEM_BROKER_SLOT_NECK 8192
#define ITEM_BROKER_SLOT_PRIMARY 1
#define ITEM_BROKER_SLOT_PRIMARY_2H 2
#define ITEM_BROKER_SLOT_RANGE_WEAPON 32768
#define ITEM_BROKER_SLOT_RING 2048
#define ITEM_BROKER_SLOT_SECONDARY 8
#define ITEM_BROKER_SLOT_SHOULDERS 64
#define ITEM_BROKER_SLOT_WAIST 131072
#define ITEM_BROKER_SLOT_WRIST 16384
#define ITEM_BROKER_STAT_TYPE_NONE 0
#define ITEM_BROKER_STAT_TYPE_DEF 2
#define ITEM_BROKER_STAT_TYPE_STR 4
#define ITEM_BROKER_STAT_TYPE_STA 8
#define ITEM_BROKER_STAT_TYPE_AGI 16
#define ITEM_BROKER_STAT_TYPE_WIS 32
#define ITEM_BROKER_STAT_TYPE_INT 64
#define ITEM_BROKER_STAT_TYPE_HEALTH 128
#define ITEM_BROKER_STAT_TYPE_POWER 256
#define ITEM_BROKER_STAT_TYPE_HEAT 512
#define ITEM_BROKER_STAT_TYPE_COLD 1024
#define ITEM_BROKER_STAT_TYPE_MAGIC 2048
#define ITEM_BROKER_STAT_TYPE_MENTAL 4096
#define ITEM_BROKER_STAT_TYPE_DIVINE 8192
#define ITEM_BROKER_STAT_TYPE_POISON 16384
#define ITEM_BROKER_STAT_TYPE_DISEASE 32768
#define ITEM_BROKER_STAT_TYPE_CRUSH 65536
#define ITEM_BROKER_STAT_TYPE_SLASH 131072
#define ITEM_BROKER_STAT_TYPE_PIERCE 262144
#define ITEM_BROKER_STAT_TYPE_CRITICAL 524288
#define ITEM_BROKER_STAT_TYPE_DBL_ATTACK 1048576
#define ITEM_BROKER_STAT_TYPE_ABILITY_MOD 2097152
#define ITEM_BROKER_STAT_TYPE_POTENCY 4194304
#define OVERFLOW_SLOT 0xFFFFFFFE
#define SLOT_INVALID 0xFFFF
#define ITEM_STAT_STR 0
#define ITEM_STAT_STA 1
#define ITEM_STAT_AGI 2
#define ITEM_STAT_WIS 3
#define ITEM_STAT_INT 4
#define ITEM_STAT_ADORNING 100
#define ITEM_STAT_AGGRESSION 101
#define ITEM_STAT_ARTIFICING 102
#define ITEM_STAT_ARTISTRY 103
#define ITEM_STAT_CHEMISTRY 104
#define ITEM_STAT_CRUSHING 105
#define ITEM_STAT_DEFENSE 106
#define ITEM_STAT_DEFLECTION 107
#define ITEM_STAT_DISRUPTION 108
#define ITEM_STAT_FISHING 109
#define ITEM_STAT_FLETCHING 110
#define ITEM_STAT_FOCUS 111
#define ITEM_STAT_FORESTING 112
#define ITEM_STAT_GATHERING 113
#define ITEM_STAT_METAL_SHAPING 114
#define ITEM_STAT_METALWORKING 115
#define ITEM_STAT_MINING 116
#define ITEM_STAT_MINISTRATION 117
#define ITEM_STAT_ORDINATION 118
#define ITEM_STAT_PARRY 119
#define ITEM_STAT_PIERCING 120
#define ITEM_STAT_RANGED 121
#define ITEM_STAT_SAFE_FALL 122
#define ITEM_STAT_SCRIBING 123
#define ITEM_STAT_SCULPTING 124
#define ITEM_STAT_SLASHING 125
#define ITEM_STAT_SUBJUGATION 126
#define ITEM_STAT_SWIMMING 127
#define ITEM_STAT_TAILORING 128
#define ITEM_STAT_TINKERING 129
#define ITEM_STAT_TRANSMUTING 130
#define ITEM_STAT_TRAPPING 131
#define ITEM_STAT_WEAPON_SKILLS 132
#define ITEM_STAT_VS_PHYSICAL 200
#define ITEM_STAT_VS_HEAT 201 //elemental
#define ITEM_STAT_VS_POISON 202 //noxious
#define ITEM_STAT_VS_MAGIC 203 //arcane
#define ITEM_STAT_VS_DROWNING 210
#define ITEM_STAT_VS_FALLING 211
#define ITEM_STAT_VS_PAIN 212
#define ITEM_STAT_VS_MELEE 213
#define ITEM_STAT_VS_SLASH 204
#define ITEM_STAT_VS_CRUSH 205
#define ITEM_STAT_VS_PIERCE 206
//#define ITEM_STAT_VS_HEAT 203 //just so no build error
#define ITEM_STAT_VS_COLD 207
//#define ITEM_STAT_VS_MAGIC 205 //just so no build error
#define ITEM_STAT_VS_MENTAL 208
#define ITEM_STAT_VS_DIVINE 209
#define ITEM_STAT_VS_DISEASE 214
//#define ITEM_STAT_VS_POISON 209 //just so no build error
//#define ITEM_STAT_VS_DROWNING 210 //just so no build error
//#define ITEM_STAT_VS_FALLING 211 //just so no build error
//#define ITEM_STAT_VS_PAIN 212 //just so no build error
//#define ITEM_STAT_VS_MELEE 213 //just so no build error
#define ITEM_STAT_DMG_SLASH 300
#define ITEM_STAT_DMG_CRUSH 301
#define ITEM_STAT_DMG_PIERCE 302
#define ITEM_STAT_DMG_HEAT 303
#define ITEM_STAT_DMG_COLD 304
#define ITEM_STAT_DMG_MAGIC 305
#define ITEM_STAT_DMG_MENTAL 306
#define ITEM_STAT_DMG_DIVINE 307
#define ITEM_STAT_DMG_DISEASE 308
#define ITEM_STAT_DMG_POISON 309
#define ITEM_STAT_DMG_DROWNING 310
#define ITEM_STAT_DMG_FALLING 311
#define ITEM_STAT_DMG_PAIN 312
#define ITEM_STAT_DMG_MELEE 313
#define ITEM_STAT_HEALTH 500
#define ITEM_STAT_POWER 501
#define ITEM_STAT_CONCENTRATION 502
#define ITEM_STAT_HPREGEN 600
#define ITEM_STAT_MANAREGEN 601
#define ITEM_STAT_HPREGENPPT 602
#define ITEM_STAT_MPREGENPPT 603
#define ITEM_STAT_COMBATHPREGENPPT 604
#define ITEM_STAT_COMBATMPREGENPPT 605
#define ITEM_STAT_MAXHP 606
#define ITEM_STAT_MAXHPPERC 607
#define ITEM_STAT_SPEED 608
#define ITEM_STAT_SLOW 609
#define ITEM_STAT_MOUNTSPEED 610
#define ITEM_STAT_MOUNTAIRSPEED 611
#define ITEM_STAT_OFFENSIVESPEED 612
#define ITEM_STAT_ATTACKSPEED 613
#define ITEM_STAT_MAXMANA 614
#define ITEM_STAT_MAXMANAPERC 615
#define ITEM_STAT_MAXATTPERC 616
#define ITEM_STAT_BLURVISION 617
#define ITEM_STAT_MAGICLEVELIMMUNITY 618
#define ITEM_STAT_HATEGAINMOD 619
#define ITEM_STAT_COMBATEXPMOD 620
#define ITEM_STAT_TRADESKILLEXPMOD 621
#define ITEM_STAT_ACHIEVEMENTEXPMOD 622
#define ITEM_STAT_SIZEMOD 623
#define ITEM_STAT_DPS 624
#define ITEM_STAT_STEALTH 625
#define ITEM_STAT_INVIS 626
#define ITEM_STAT_SEESTEALTH 627
#define ITEM_STAT_SEEINVIS 628
#define ITEM_STAT_EFFECTIVELEVELMOD 629
#define ITEM_STAT_RIPOSTECHANCE 630
#define ITEM_STAT_PARRYCHANCE 631
#define ITEM_STAT_DODGECHANCE 632
#define ITEM_STAT_AEAUTOATTACKCHANCE 633
#define ITEM_STAT_MULTIATTACKCHANCE 634
#define ITEM_STAT_SPELLMULTIATTACKCHANCE 635
#define ITEM_STAT_FLURRY 636
#define ITEM_STAT_MELEEDAMAGEMULTIPLIER 637
#define ITEM_STAT_EXTRAHARVESTCHANCE 638
#define ITEM_STAT_EXTRASHIELDBLOCKCHANCE 639
#define ITEM_STAT_ITEMHPREGENPPT 640
#define ITEM_STAT_ITEMPPREGENPPT 641
#define ITEM_STAT_MELEECRITCHANCE 642
#define ITEM_STAT_CRITAVOIDANCE 643
#define ITEM_STAT_BENEFICIALCRITCHANCE 644
#define ITEM_STAT_CRITBONUS 645
#define ITEM_STAT_POTENCY 646
#define ITEM_STAT_UNCONSCIOUSHPMOD 647
#define ITEM_STAT_ABILITYREUSESPEED 648
#define ITEM_STAT_ABILITYRECOVERYSPEED 649
#define ITEM_STAT_ABILITYCASTINGSPEED 650
#define ITEM_STAT_SPELLREUSESPEED 651
#define ITEM_STAT_MELEEWEAPONRANGE 652
#define ITEM_STAT_RANGEDWEAPONRANGE 653
#define ITEM_STAT_FALLINGDAMAGEREDUCTION 654
#define ITEM_STAT_RIPOSTEDAMAGE 655
#define ITEM_STAT_MINIMUMDEFLECTIONCHANCE 656
#define ITEM_STAT_MOVEMENTWEAVE 657
#define ITEM_STAT_COMBATHPREGEN 658
#define ITEM_STAT_COMBATMANAREGEN 659
#define ITEM_STAT_CONTESTSPEEDBOOST 660
#define ITEM_STAT_TRACKINGAVOIDANCE 661
#define ITEM_STAT_STEALTHINVISSPEEDMOD 662
#define ITEM_STAT_LOOT_COIN 663
#define ITEM_STAT_ARMORMITIGATIONINCREASE 664
#define ITEM_STAT_AMMOCONSERVATION 665
#define ITEM_STAT_STRIKETHROUGH 666
#define ITEM_STAT_STATUSBONUS 667
#define ITEM_STAT_ACCURACY 668
#define ITEM_STAT_COUNTERSTRIKE 669
#define ITEM_STAT_SHIELDBASH 670
#define ITEM_STAT_WEAPONDAMAGEBONUS 671
#define ITEM_STAT_ADDITIONALRIPOSTECHANCE 672
#define ITEM_STAT_CRITICALMITIGATION 673
#define ITEM_STAT_PVPTOUGHNESS 674
#define ITEM_STAT_STAMINABONUS 675
#define ITEM_STAT_WISDOMITBONUS 676
#define ITEM_STAT_HEALRECEIVE 677
#define ITEM_STAT_HEALRECEIVEPERC 678
#define ITEM_STAT_PVPCRITICALMITIGATION 679
#define ITEM_STAT_BASEAVOIDANCEBONUS 680
//#define ITEM_STAT_HPREGEN 600
//#define ITEM_STAT_MANAREGEN 601
//#define ITEM_STAT_HPREGENPPT 602
//#define ITEM_STAT_MPREGENPPT 603
//#define ITEM_STAT_COMBATHPREGENPPT 604
//#define ITEM_STAT_COMBATMPREGENPPT 605
//#define ITEM_STAT_MAXHP 606
//#define ITEM_STAT_MAXHPPERC 607
//#define ITEM_STAT_SPEED 608
//#define ITEM_STAT_SLOW 609
//#define ITEM_STAT_MOUNTSPEED 610
//#define ITEM_STAT_OFFENSIVESPEED 611
//#define ITEM_STAT_ATTACKSPEED 612
//#define ITEM_STAT_MAXMANA 613
//#define ITEM_STAT_MAXMANAPERC 614
//#define ITEM_STAT_MAXATTPERC 615
//#define ITEM_STAT_BLURVISION 616
//#define ITEM_STAT_MAGICLEVELIMMUNITY 617
//#define ITEM_STAT_HATEGAINMOD 618
//#define ITEM_STAT_COMBATEXPMOD 619
//#define ITEM_STAT_TRADESKILLEXPMOD 620
//#define ITEM_STAT_ACHIEVEMENTEXPMOD 621
//#define ITEM_STAT_SIZEMOD 622
//#define ITEM_STAT_UNKNOWN 623
//#define ITEM_STAT_STEALTH 624
//#define ITEM_STAT_INVIS 625
//#define ITEM_STAT_SEESTEALTH 626
//#define ITEM_STAT_SEEINVIS 627
//#define ITEM_STAT_EFFECTIVELEVELMOD 628
//#define ITEM_STAT_RIPOSTECHANCE 629
//#define ITEM_STAT_PARRYCHANCE 630
//#define ITEM_STAT_DODGECHANCE 631
//#define ITEM_STAT_AEAUTOATTACKCHANCE 632
//#define ITEM_STAT_DOUBLEATTACKCHANCE 633
//#define ITEM_STAT_RANGEDDOUBLEATTACKCHANCE 634
//#define ITEM_STAT_SPELLDOUBLEATTACKCHANCE 635
//#define ITEM_STAT_FLURRY 636
//#define ITEM_STAT_EXTRAHARVESTCHANCE 637
//#define ITEM_STAT_EXTRASHIELDBLOCKCHANCE 638
#define ITEM_STAT_DEFLECTIONCHANCE 400 //just so no build error
//#define ITEM_STAT_ITEMHPREGENPPT 640
//#define ITEM_STAT_ITEMPPREGENPPT 641
//#define ITEM_STAT_MELEECRITCHANCE 642
//#define ITEM_STAT_RANGEDCRITCHANCE 643
//#define ITEM_STAT_DMGSPELLCRITCHANCE 644
//#define ITEM_STAT_HEALSPELLCRITCHANCE 645
//#define ITEM_STAT_MELEECRITBONUS 646
//#define ITEM_STAT_RANGEDCRITBONUS 647
//#define ITEM_STAT_DMGSPELLCRITBONUS 648
//#define ITEM_STAT_HEALSPELLCRITBONUS 649
//#define ITEM_STAT_UNCONSCIOUSHPMOD 650
//#define ITEM_STAT_SPELLTIMEREUSEPCT 651
//#define ITEM_STAT_SPELLTIMERECOVERYPCT 652
//#define ITEM_STAT_SPELLTIMECASTPCT 653
//#define ITEM_STAT_MELEEWEAPONRANGE 654
//#define ITEM_STAT_RANGEDWEAPONRANGE 655
//#define ITEM_STAT_FALLINGDAMAGEREDUCTION 656
//#define ITEM_STAT_SHIELDEFFECTIVENESS 657
//#define ITEM_STAT_RIPOSTEDAMAGE 658
//#define ITEM_STAT_MINIMUMDEFLECTIONCHANCE 659
//#define ITEM_STAT_MOVEMENTWEAVE 660
//#define ITEM_STAT_COMBATHPREGEN 661
//#define ITEM_STAT_COMBATMANAREGEN 662
//#define ITEM_STAT_CONTESTSPEEDBOOST 663
//#define ITEM_STAT_TRACKINGAVOIDANCE 664
//#define ITEM_STAT_STEALTHINVISSPEEDMOD 665
//#define ITEM_STAT_LOOT_COIN 666
//#define ITEM_STAT_ARMORMITIGATIONINCREASE 667
//#define ITEM_STAT_AMMOCONSERVATION 668
//#define ITEM_STAT_STRIKETHROUGH 669
//#define ITEM_STAT_STATUSBONUS 670
//#define ITEM_STAT_ACCURACY 671
//#define ITEM_STAT_COUNTERSTRIKE 672
//#define ITEM_STAT_SHIELDBASH 673
//#define ITEM_STAT_WEAPONDAMAGEBONUS 674
//#define ITEM_STAT_ADDITIONALRIPOSTECHANCE 675
//#define ITEM_STAT_CRITICALMITIGATION 676
//#define ITEM_STAT_COMBATARTDAMAGE 677
//#define ITEM_STAT_SPELLDAMAGE 678
//#define ITEM_STAT_HEALAMOUNT 679
//#define ITEM_STAT_TAUNTAMOUNT 680
#define ITEM_STAT_SPELL_DAMAGE 700
#define ITEM_STAT_HEAL_AMOUNT 701
#define ITEM_STAT_SPELL_AND_HEAL 702
#define ITEM_STAT_COMBAT_ART_DAMAGE 703
#define ITEM_STAT_SPELL_AND_COMBAT_ART_DAMAGE 704
#define ITEM_STAT_TAUNT_AMOUNT 705
#define ITEM_STAT_TAUNT_AND_COMBAT_ART_DAMAGE 706
#define ITEM_STAT_ABILITY_MODIFIER 707
#pragma pack(1)
struct ItemStatsValues{
sint16 str;
sint16 sta;
sint16 agi;
sint16 wis;
sint16 int_;
sint16 vs_slash;
sint16 vs_crush;
sint16 vs_pierce;
sint16 vs_heat;
sint16 vs_cold;
sint16 vs_magic;
sint16 vs_mental;
sint16 vs_divine;
sint16 vs_disease;
sint16 vs_poison;
sint16 health;
sint16 power;
sint8 concentration;
sint16 ability_modifier;
sint16 criticalmitigation;
sint16 extrashieldblockchance;
sint16 beneficialcritchance;
sint16 critbonus;
sint16 potency;
sint16 hategainmod;
sint16 abilityreusespeed;
sint16 abilitycastingspeed;
sint16 abilityrecoveryspeed;
sint16 spellreusespeed;
sint16 spellmultiattackchance;
sint16 dps;
sint16 attackspeed;
sint16 multiattackchance;
sint16 aeautoattackchance;
sint16 strikethrough;
sint16 accuracy;
sint16 offensivespeed;
};
struct ItemCore{
int32 item_id;
sint32 soe_id;
int32 bag_id;
sint32 inv_slot_id;
sint16 slot_id;
int8 index;
int16 icon;
int16 count;
int8 tier;
int8 num_slots;
int32 unique_id;
int8 num_free_slots;
int16 recommended_level;
bool item_locked;
};
#pragma pack()
struct ItemStat{
string stat_name;
int8 stat_type;
sint16 stat_subtype;
int16 stat_type_combined;
float value;
};
struct ItemLevelOverride{
int8 adventure_class;
int8 tradeskill_class;
int16 level;
};
struct ItemClass{
int8 adventure_class;
int8 tradeskill_class;
int16 level;
};
struct ItemAppearance{
int16 type;
int8 red;
int8 green;
int8 blue;
int8 highlight_red;
int8 highlight_green;
int8 highlight_blue;
};
class PlayerItemList;
class Item{
public:
#pragma pack(1)
struct ItemStatString{
EQ2_8BitString stat_string;
};
struct Generic_Info{
int8 show_name;
int8 creator_flag;
int16 item_flags;
int8 condition;
int32 weight; // num/10
int32 skill_req1;
int32 skill_req2;
int16 skill_min;
int8 item_type; //0=normal, 1=weapon, 2=range, 3=armor, 4=shield, 5=bag, 6=scroll, 7=recipe, 8=food, 9=bauble, 10=house item, 11=thrown, 12=house container, 13=adormnet, 14=??, 16=profile, 17=patter set, 18=item set, 19=book, 20=decoration, 21=dungeon maker, 22=marketplace
int16 appearance_id;
int8 appearance_red;
int8 appearance_green;
int8 appearance_blue;
int8 appearance_highlight_red;
int8 appearance_highlight_green;
int8 appearance_highlight_blue;
int8 collectable;
int32 offers_quest_id;
int32 part_of_quest_id;
int16 max_charges;
int8 display_charges;
int64 adventure_classes;
int64 tradeskill_classes;
int16 adventure_default_level;
int16 tradeskill_default_level;
int8 usable;
};
struct Armor_Info {
int16 mitigation_low;
int16 mitigation_high;
};
struct Weapon_Info {
int16 wield_type;
int16 damage_low1;
int16 damage_high1;
int16 damage_low2;
int16 damage_high2;
int16 damage_low3;
int16 damage_high3;
int16 delay;
float rating;
};
struct Shield_Info {
Armor_Info armor_info;
};
struct Ranged_Info {
Weapon_Info weapon_info;
int16 range_low;
int16 range_high;
};
struct Bag_Info {
int8 num_slots;
int16 weight_reduction;
};
struct Food_Info{
int8 type; //0=water, 1=food
int8 level;
float duration;
int8 satiation;
};
struct Bauble_Info{
int16 cast;
int16 recovery;
int32 duration;
float recast;
int8 display_slot_optional;
int8 display_cast_time;
int8 display_bauble_type;
float effect_radius;
int32 max_aoe_targets;
int8 display_until_cancelled;
};
struct Book_Info{
int8 language;
EQ2_16BitString author;
EQ2_16BitString title;
};
struct Skill_Info{
int32 spell_id;
int32 spell_tier;
};
struct House_Info{
int32 status_rent_reduction;
};
struct HouseContainer_Info{
int16 disallowed_types;
int16 allowed_types;
int8 num_slots;
};
struct RecipeBook_Info{
vector<string> recipes;
int8 uses;
};
struct Thrown_Info{
sint32 range;
sint32 damage_modifier;
float hit_bonus;
int32 damage_type;
};
struct ItemEffect{
EQ2_16BitString effect;
int8 percentage;
int8 subbulletflag;
};
#pragma pack()
Item();
Item(Item* in_item);
~Item();
string lowername;
string name;
string description;
int8 stack_count;
int32 sell_price;
int32 max_sell_value;
bool save_needed;
int8 weapon_type;
string adornment;
string creator;
vector<ItemStat*> item_stats;
vector<ItemStatString*> item_string_stats;
vector<ItemLevelOverride*> item_level_overrides;
vector<ItemEffect*> item_effects;
Generic_Info generic_info;
Weapon_Info* weapon_info;
Ranged_Info* ranged_info;
Armor_Info* armor_info;
Bag_Info* bag_info;
Food_Info* food_info;
Bauble_Info* bauble_info;
Book_Info* book_info;
Skill_Info* skill_info;
RecipeBook_Info* recipebook_info;
Thrown_Info* thrown_info;
vector<int8> slot_data;
ItemCore details;
int32 spell_id;
int8 spell_tier;
string item_script;
void AddEffect(string effect, int8 percentage, int8 subbulletflag);
int32 GetMaxSellValue();
void SetMaxSellValue(int32 val);
void SetItem(Item* old_item);
int16 GetOverrideLevel(int8 adventure_class, int8 tradeskill_class);
void AddLevelOverride(int8 adventure_class, int8 tradeskill_class, int16 level);
void AddLevelOverride(ItemLevelOverride* class_);
bool CheckClassLevel(int8 adventure_class, int8 tradeskill_class, int16 level);
bool CheckClass(int8 adventure_class, int8 tradeskill_class);
bool CheckLevel(int8 adventure_class, int8 tradeskill_class, int16 level);
void SetAppearance(int16 type, int8 red, int8 green, int8 blue, int8 highlight_red, int8 highlight_green, int8 highlight_blue);
void SetAppearance(ItemAppearance* appearance);
void AddStat(ItemStat* in_stat);
void AddStatString(ItemStatString* in_stat);
void AddStat(int8 type, int16 subtype, float value, char* name = 0);
void SetWeaponType(int8 type);
int8 GetWeaponType();
bool HasSlot(int8 slot, int8 slot2 = 255);
bool IsNormal();
bool IsWeapon();
bool IsArmor();
bool IsRanged();
bool IsBag();
bool IsFood();
bool IsBauble();
bool IsSkill();
bool IsHouseItem();
bool IsHouseContainer();
bool IsShield();
bool IsAdornment();
bool IsAmmo();
bool IsBook();
bool IsChainArmor();
bool IsClothArmor();
bool IsCollectable();
bool IsCloak();
bool IsCrushWeapon();
bool IsFoodFood();
bool IsFoodDrink();
bool IsJewelry();
bool IsLeatherArmor();
bool IsMisc();
bool IsPierceWeapon();
bool IsPlateArmor();
bool IsPoison();
bool IsPotion();
bool IsRecipeBook();
bool IsSalesDisplay();
bool IsSlashWeapon();
bool IsSpellScroll();
bool IsTinkered();
bool IsTradeskill();
bool IsThrown();
void SetItemScript(string name);
const char* GetItemScript();
int32 CalculateRepairCost();
void SetItemType(int8 in_type);
void serialize(PacketStruct* packet, bool show_name = false, Player* player = 0, int16 packet_type = 0, int8 subtype = 0, bool loot_item = false);
EQ2Packet* serialize(int16 version, bool show_name = false, Player* player = 0, bool include_twice = true, int16 packet_type = 0, int8 subtype = 0, bool merchant_item = false, bool loot_item = false);
PacketStruct* PrepareItem(int16 version, bool merchant_item = false, bool loot_item = false);
bool CheckFlag(int32 flag);
void AddSlot(int8 slot_id);
void SetSlots(int32 slots);
bool needs_deletion;
};
class MasterItemList{
public:
~MasterItemList();
map<int32,Item*> items;
Item* GetItem(int32 id);
Item* GetItemByName(const char *name);
ItemStatsValues* CalculateItemBonuses(int32 item_id, Entity* entity = 0);
ItemStatsValues* CalculateItemBonuses(Item* desc, Entity* entity = 0, ItemStatsValues* values = 0);
vector<Item*>* GetItems(string name, int32 itype, int32 ltype, int32 btype, int64 minprice, int64 maxprice, int8 minskill, int8 maxskill, string seller, string adornment, int8 mintier, int8 maxtier, int16 minlevel, int16 maxlevel, sint8 itemclass);
vector<Item*>* GetItems(map<string, string> criteria);
void AddItem(Item* item);
bool IsBag(int32 item_id);
void RemoveAll();
static int32 NextUniqueID();
static void ResetUniqueID(int32 new_id);
static int32 next_unique_id;
};
class PlayerItemList {
public:
PlayerItemList();
~PlayerItemList();
// int16 number;
map<int32, Item*> indexed_items;
map<sint32, map<int16, Item*> > items;
// map< int8, Item* > inv_items;
// map< int8, Item* > bank_items;
bool SharedBankAddAllowed(Item* item);
vector<Item*>* GetItemsFromBagID(sint32 bag_id);
vector<Item*>* GetItemsInBag(Item* bag);
Item* GetBag(int8 inventory_slot, bool lock = true);
bool HasItem(int32 id, bool include_bank = false);
Item* GetItemFromIndex(int32 index);
void MoveItem(Item* item, sint32 inv_slot, int16 slot, bool erase_old = true);
bool MoveItem(sint32 to_bag_id, int16 from_index, sint8 to, int8 charges);
Item* GetItemFromUniqueID(int32 item_id, bool include_bank = false, bool lock = true);
Item* GetItemFromID(int32 item_id, int8 count = 0, bool include_bank = false, bool lock = true);
bool AssignItemToFreeSlot(Item* item);
int16 GetNumberOfFreeSlots();
int16 GetNumberOfItems();
bool HasFreeSlot();
bool HasFreeBagSlot();
void DestroyItem(int16 index);
Item* CanStack(Item* item, bool include_bank = false);
void RemoveItem(Item* item, bool delete_item = false);
void AddItem(Item* item);
Item* GetItem(sint32 bag_slot, int16 slot);
EQ2Packet* serialize(Player* player, int16 version);
uchar* xor_packet;
uchar* orig_packet;
map<int32, Item*>* GetAllItems();
bool HasFreeBankSlot();
int8 FindFreeBankSlot();
///<summary>Get the first free slot and stor them in the provided variables</summary>
///<param name='bag_id'>Will contain the bag id of the first free spot</param>
///<param name='slot'>Will contain the slot id of the first free slot</param>
///<returns>True if a free slot was found</returns>
bool GetFirstFreeSlot(sint32* bag_id, sint16* slot);
private:
void Stack(Item* orig_item, Item* item);
Mutex MPlayerItems;
int16 packet_count;
};
class OverFlowItemList : public PlayerItemList {
public:
bool OverFlowSlotFull();
int8 GetNextOverFlowSlot();
bool AddItem(Item* item);
Item* GetOverFlowItem();
};
class EquipmentItemList{
public:
EquipmentItemList();
EquipmentItemList(const EquipmentItemList& list);
~EquipmentItemList();
Item* items[NUM_SLOTS];
vector<Item*>* GetAllEquippedItems();
bool HasItem(int32 id);
int8 GetNumberOfItems();
Item* GetItemFromUniqueID(int32 item_id);
Item* GetItemFromItemID(int32 item_id);
void SetItem(int8 slot_id, Item* item);
void RemoveItem(int8 slot, bool delete_item = false);
Item* GetItem(int8 slot_id);
bool AddItem(int8 slot, Item* item);
bool CheckEquipSlot(Item* tmp, int8 slot);
bool CanItemBeEquippedInSlot(Item* tmp, int8 slot);
int8 GetFreeSlot(Item* tmp, int8 slot_id = 255);
ItemStatsValues* CalculateEquipmentBonuses(Entity* entity = 0);
EQ2Packet* serialize(int16 version);
uchar* xor_packet;
uchar* orig_packet;
private:
Mutex MEquipmentItems;
};
#endif

View File

@ -0,0 +1,211 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
//Item Stat defines for ToV Client
//Stat type 6 (blue stats)
#define TOV_ITEM_STAT_HPREGEN 600
#define TOV_ITEM_STAT_MANAREGEN 601
#define TOV_ITEM_STAT_HPREGENPPT 602
#define TOV_ITEM_STAT_MPREGENPPT 603
#define TOV_ITEM_STAT_COMBATHPREGENPPT 604
#define TOV_ITEM_STAT_COMBATMPREGENPPT 605
#define TOV_ITEM_STAT_MAXHP 606
#define TOV_ITEM_STAT_MAXHPPERC 607
#define TOV_ITEM_STAT_MAXHPPERCFINAL 608
#define TOV_ITEM_STAT_SPEED 609
#define TOV_ITEM_STAT_SLOW 610
#define TOV_ITEM_STAT_MOUNTSPEED 611
#define TOV_ITEM_STAT_MOUNTAIRSPEED 612
#define TOV_ITEM_STAT_LEAPSPEED 613
#define TOV_ITEM_STAT_LEAPTIME 614
#define TOV_ITEM_STAT_GLIDEEFFICIENCY 615
#define TOV_ITEM_STAT_OFFENSIVESPEED 616
#define TOV_ITEM_STAT_ATTACKSPEED 617
#define TOV_ITEM_STAT_MAXMANA 618
#define TOV_ITEM_STAT_MAXMANAPERC 619
#define TOV_ITEM_STAT_MAXATTPERC 620
#define TOV_ITEM_STAT_BLURVISION 621
#define TOV_ITEM_STAT_MAGICLEVELIMMUNITY 622
#define TOV_ITEM_STAT_HATEGAINMOD 623
#define TOV_ITEM_STAT_COMBATEXPMOD 624
#define TOV_ITEM_STAT_TRADESKILLEXPMOD 625
#define TOV_ITEM_STAT_ACHIEVEMENTEXPMOD 626
#define TOV_ITEM_STAT_SIZEMOD 627
#define TOV_ITEM_STAT_DPS 628
#define TOV_ITEM_STAT_STEALTH 629
#define TOV_ITEM_STAT_INVIS 630
#define TOV_ITEM_STAT_SEESTEALTH 631
#define TOV_ITEM_STAT_SEEINVIS 632
#define TOV_ITEM_STAT_EFFECTIVELEVELMOD 633
#define TOV_ITEM_STAT_RIPOSTECHANCE 634
#define TOV_ITEM_STAT_PARRYCHANCE 635
#define TOV_ITEM_STAT_DODGECHANCE 636
#define TOV_ITEM_STAT_AEAUTOATTACKCHANCE 637
#define TOV_ITEM_STAT_MULTIATTACKCHANCE 638 //DOUBLEATTACKCHANCE
#define TOV_ITEM_STAT_SPELLMULTIATTACKCHANCE 639
#define TOV_ITEM_STAT_FLURRY 640
#define TOV_ITEM_STAT_MELEEDAMAGEMULTIPLIER 641
#define TOV_ITEM_STAT_EXTRAHARVESTCHANCE 642
#define TOV_ITEM_STAT_EXTRASHIELDBLOCKCHANCE 643
#define TOV_ITEM_STAT_ITEMHPREGENPPT 644
#define TOV_ITEM_STAT_ITEMPPREGENPPT 645
#define TOV_ITEM_STAT_MELEECRITCHANCE 646
#define TOV_ITEM_STAT_CRITAVOIDANCE 647
#define TOV_ITEM_STAT_BENEFICIALCRITCHANCE 648
#define TOV_ITEM_STAT_CRITBONUS 649
#define TOV_ITEM_STAT_POTENCY 650 //BASEMODIFIER
#define TOV_ITEM_STAT_UNCONSCIOUSHPMOD 651
#define TOV_ITEM_STAT_ABILITYREUSESPEED 652 //SPELLTIMEREUSEPCT
#define TOV_ITEM_STAT_ABILITYRECOVERYSPEED 653 //SPELLTIMERECOVERYPCT
#define TOV_ITEM_STAT_ABILITYCASTINGSPEED 654 //SPELLTIMECASTPCT
#define TOV_ITEM_STAT_SPELLREUSESPEED 655 //SPELLTIMEREUSESPELLONLY
#define TOV_ITEM_STAT_MELEEWEAPONRANGE 656
#define TOV_ITEM_STAT_RANGEDWEAPONRANGE 657
#define TOV_ITEM_STAT_FALLINGDAMAGEREDUCTION 658
#define TOV_ITEM_STAT_RIPOSTEDAMAGE 659
#define TOV_ITEM_STAT_MINIMUMDEFLECTIONCHANCE 660
#define TOV_ITEM_STAT_MOVEMENTWEAVE 661
#define TOV_ITEM_STAT_COMBATHPREGEN 662
#define TOV_ITEM_STAT_COMBATMANAREGEN 663
#define TOV_ITEM_STAT_CONTESTSPEEDBOOST 664
#define TOV_ITEM_STAT_TRACKINGAVOIDANCE 665
#define TOV_ITEM_STAT_STEALTHINVISSPEEDMOD 666
#define TOV_ITEM_STAT_LOOT_COIN 667
#define TOV_ITEM_STAT_ARMORMITIGATIONINCREASE 668
#define TOV_ITEM_STAT_AMMOCONSERVATION 669
#define TOV_ITEM_STAT_STRIKETHROUGH 670
#define TOV_ITEM_STAT_STATUSBONUS 671
#define TOV_ITEM_STAT_ACCURACY 672
#define TOV_ITEM_STAT_COUNTERSTRIKE 673
#define TOV_ITEM_STAT_SHIELDBASH 674
#define TOV_ITEM_STAT_WEAPONDAMAGEBONUS 675
#define TOV_ITEM_STAT_SPELLWEAPONDAMAGEBONUS 676
#define TOV_ITEM_STAT_WEAPONDAMAGEBONUSMELEEONLY 677
#define TOV_ITEM_STAT_ADDITIONALRIPOSTECHANCE 678
#define TOV_ITEM_STAT_PVPTOUGHNESS 680
#define TOV_ITEM_STAT_PVPLETHALITY 681
#define TOV_ITEM_STAT_STAMINABONUS 682
#define TOV_ITEM_STAT_WISDOMMITBONUS 683
#define TOV_ITEM_STAT_HEALRECEIVE 684
#define TOV_ITEM_STAT_HEALRECEIVEPERC 685
#define TOV_ITEM_STAT_PVPCRITICALMITIGATION 686
#define TOV_ITEM_STAT_BASEAVOIDANCEBONUS 687
#define TOV_ITEM_STAT_INCOMBATSAVAGERYREGEN 688
#define TOV_ITEM_STAT_OUTOFCOMBATSAVAGERYREGEN 689
#define TOV_ITEM_STAT_SAVAGERYREGEN 690
#define TOV_ITEM_STAT_SAVAGERYGAINMOD 691
#define TOV_ITEM_STAT_MAXSAVAGERYLEVEL 692
#define TOV_ITEM_STAT_INCOMBATDISSONANCEREGEN 693
#define TOV_ITEM_STAT_OUTOFCOMBATDISSONANCEREGEN 694
#define TOV_ITEM_STAT_DISSONANCEREGEN 695
#define TOV_ITEM_STAT_DISSONANCEGAINMOD 696
#define TOV_ITEM_STAT_AEAUTOATTACKAVOID 697
//End of stat type 6 (blue stats)
//Item stat type 5 (health,power,savagery,dissonance,concentration)
#define TOV_ITEM_STAT_HEALTH 500
#define TOV_ITEM_STAT_POWER 501
#define TOV_ITEM_STAT_CONCENTRATION 502
#define TOV_ITEM_STAT_SAVAGERY 503
#define TOV_ITEM_STAT_DISSONANCE 504
//End of stat type 5
//Item stat type 3 (damage mods)
#define TOV_ITEM_STAT_DMG_SLASH 300
#define TOV_ITEM_STAT_DMG_CRUSH 301
#define TOV_ITEM_STAT_DMG_PIERCE 302
#define TOV_ITEM_STAT_DMG_HEAT 303
#define TOV_ITEM_STAT_DMG_COLD 304
#define TOV_ITEM_STAT_DMG_MAGIC 305
#define TOV_ITEM_STAT_DMG_MENTAL 306
#define TOV_ITEM_STAT_DMG_DIVINE 307
#define TOV_ITEM_STAT_DMG_DISEASE 308
#define TOV_ITEM_STAT_DMG_POISON 309
#define TOV_ITEM_STAT_DMG_DROWNING 310
#define TOV_ITEM_STAT_DMG_FALLING 311
#define TOV_ITEM_STAT_DMG_PAIN 312
#define TOV_ITEM_STAT_DMG_MELEE 313
//End of item stat 3
#define TOV_ITEM_STAT_DEFLECTIONCHANCE 400 //just so no build error
// Other stats not listed above (not sent from the server), never send these to the client
// using type 8 as it is not used by the client as far as we know
#define TOV_ITEM_STAT_DURABILITY_MOD 800
#define TOV_ITEM_STAT_DURABILITY_ADD 801
#define TOV_ITEM_STAT_PROGRESS_ADD 802
#define TOV_ITEM_STAT_PROGRESS_MOD 803
#define TOV_ITEM_STAT_SUCCESS_MOD 804
#define TOV_ITEM_STAT_CRIT_SUCCESS_MOD 805
#define TOV_ITEM_STAT_EX_DURABILITY_MOD 806
#define TOV_ITEM_STAT_EX_DURABILITY_ADD 807
#define TOV_ITEM_STAT_EX_PROGRESS_MOD 808
#define TOV_ITEM_STAT_EX_PROGRESS_ADD 809
#define TOV_ITEM_STAT_EX_SUCCESS_MOD 810
#define TOV_ITEM_STAT_EX_CRIT_SUCCESS_MOD 811
#define TOV_ITEM_STAT_EX_CRIT_FAILURE_MOD 812
#define TOV_ITEM_STAT_RARE_HARVEST_CHANCE 813
#define TOV_ITEM_STAT_MAX_CRAFTING 814
#define TOV_ITEM_STAT_COMPONENT_REFUND 815
#define TOV_ITEM_STAT_BOUNTIFUL_HARVEST 816
#define TOV_ITEM_STAT_STR 0
#define TOV_ITEM_STAT_STA 1
#define TOV_ITEM_STAT_AGI 2
#define TOV_ITEM_STAT_WIS 3
#define TOV_ITEM_STAT_INT 4
#define TOV_ITEM_STAT_ADORNING 100
#define TOV_ITEM_STAT_AGGRESSION 101
#define TOV_ITEM_STAT_ARTIFICING 102
#define TOV_ITEM_STAT_ARTISTRY 103
#define TOV_ITEM_STAT_CHEMISTRY 104
#define TOV_ITEM_STAT_CRUSHING 105
#define TOV_ITEM_STAT_DEFENSE 106
#define TOV_ITEM_STAT_DEFLECTION 107
#define TOV_ITEM_STAT_DISRUPTION 108
#define TOV_ITEM_STAT_FISHING 109
#define TOV_ITEM_STAT_FLETCHING 110
#define TOV_ITEM_STAT_FOCUS 111
#define TOV_ITEM_STAT_FORESTING 112
#define TOV_ITEM_STAT_GATHERING 113
#define TOV_ITEM_STAT_METAL_SHAPING 114
#define TOV_ITEM_STAT_METALWORKING 115
#define TOV_ITEM_STAT_MINING 116
#define TOV_ITEM_STAT_MINISTRATION 117
#define TOV_ITEM_STAT_ORDINATION 118
#define TOV_ITEM_STAT_PARRY 119
#define TOV_ITEM_STAT_PIERCING 120
#define TOV_ITEM_STAT_RANGED 121
#define TOV_ITEM_STAT_SAFE_FALL 122
#define TOV_ITEM_STAT_SCRIBING 123
#define TOV_ITEM_STAT_SCULPTING 124
#define TOV_ITEM_STAT_SLASHING 125
#define TOV_ITEM_STAT_SUBJUGATION 126
#define TOV_ITEM_STAT_SWIMMING 127
#define TOV_ITEM_STAT_TAILORING 128
#define TOV_ITEM_STAT_TINKERING 129
#define TOV_ITEM_STAT_TRANSMUTING 130
#define TOV_ITEM_STAT_TRAPPING 131
#define TOV_ITEM_STAT_WEAPON_SKILLS 132

View File

@ -0,0 +1,134 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Loot.h"
#include "../client.h"
#include "../../common/config_reader.hpp"
#include "../classes.h"
#include "../../common/debug.hpp"
#include "../zoneserver.h"
#include "../Skills.h"
#include "../classes.h"
#include "../World.h"
#include "../LuaInterface.h"
#include "../../common/Log.h"
#include "../Entity.h"
#include "../Rules/Rules.h"
extern Classes classes;
extern ConfigReader configReader;
extern MasterSkillList master_skill_list;
extern RuleManager rule_manager;
// If we want to transfer functions to this file then we should just do it, but for now we don't need all this commented code here
NPC* Entity::DropChest() {
// Check to see if treasure chests are disabled in the rules
if (rule_manager.GetZoneRule(GetZoneID(), R_World, TreasureChestDisabled)->GetBool())
return 0;
if(GetChestDropTime()) {
return 0; // this is a chest! It doesn't drop itself!
}
NPC* chest = 0;
chest = new NPC();
chest->SetAttackable(0);
chest->SetShowLevel(0);
chest->SetShowName(1);
chest->SetTargetable(1);
chest->SetLevel(GetLevel());
chest->SetChestDropTime();
chest->SetTotalHP(100);
chest->SetHP(100);
chest->SetAlive(false);
// Set the brain to a blank brain so it does nothing
chest->SetBrain(new BlankBrain(chest));
// Set the x, y, z, heading, location (grid id) to that of the dead spawn
chest->SetZone(GetZone());
// heading needs to be GetHeading() - 180 so the chest faces the proper way
chest->SetHeading(GetHeading() - 180);
// Set the primary command to loot and the secondary to disarm
chest->AddPrimaryEntityCommand("loot", rule_manager.GetZoneRule(GetZoneID(), R_Loot, LootRadius)->GetFloat(), "loot", "", 0, 0);
chest->AddSecondaryEntityCommand("Disarm", rule_manager.GetZoneRule(GetZoneID(), R_Loot, LootRadius)->GetFloat(), "Disarm", "", 0, 0);
// 32 = loot icon for the mouse
chest->SetIcon(32);
// 1 = show the right click menu
chest->SetShowCommandIcon(1);
chest->SetLootMethod(this->GetLootMethod(), this->GetLootRarity(), this->GetLootGroupID());
chest->SetLootName(this->GetName());
int8 highest_tier = 0;
vector<Item*>::iterator itr;
for (itr = ((Spawn*)this)->GetLootItems()->begin(); itr != ((Spawn*)this)->GetLootItems()->end(); ) {
if ((*itr)->details.tier >= ITEM_TAG_COMMON && !(*itr)->IsBodyDrop()) {
if ((*itr)->details.tier > highest_tier)
highest_tier = (*itr)->details.tier;
// Add the item to the chest
chest->AddLootItem((*itr)->details.item_id, (*itr)->details.count);
// Remove the item from the corpse
itr = ((Spawn*)this)->GetLootItems()->erase(itr);
}
else
itr++;
}
/*4034 = small chest | 5864 = treasure chest | 5865 = ornate treasure chest | 4015 = exquisite chest*/
if (highest_tier >= ITEM_TAG_FABLED) {
chest->SetModelType(4015);
chest->SetName("Exquisite Chest");
}
else if (highest_tier >= ITEM_TAG_LEGENDARY) {
chest->SetModelType(5865);
chest->SetName("Ornate Chest");
}
else if (highest_tier >= ITEM_TAG_TREASURED) {
chest->SetModelType(5864);
chest->SetName("Treasure Chest");
}
else if (highest_tier >= ITEM_TAG_COMMON) {
chest->SetModelType(4034);
chest->SetName("Small Chest");
}
else {
safe_delete(chest);
chest = nullptr;
}
if (chest) {
chest->SetID(Spawn::NextID());
chest->SetShowHandIcon(1);
chest->SetLocation(GetLocation());
chest->SetX(GetX());
chest->SetZ(GetZ());
((Entity*)chest)->GetInfoStruct()->set_flying_type(false);
chest->is_flying_creature = false;
if(GetMap()) {
auto loc = glm::vec3(GetX(), GetZ(), GetY());
float new_z = FindBestZ(loc, nullptr);
chest->appearance.pos.Y = new_z; // don't use SetY here can cause a loop
}
else {
chest->appearance.pos.Y = GetY();
}
}
return chest;
}

View File

@ -0,0 +1,23 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __EQ2_LOOT__
#define __EQ2_LOOT__
#endif

View File

@ -0,0 +1,210 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../../common/Log.h"
#include "../Worlddatabase.hpp"
#include "../World.h"
extern World world;
void WorldDatabase::LoadLoot(ZoneServer* zone)
{
// First, clear previous loot tables...
zone->ClearLootTables();
DatabaseResult result;
int32 count = 0;
if (database_new.Select(&result, "SELECT id, name, mincoin, maxcoin, maxlootitems, lootdrop_probability, coin_probability FROM loottable")) {
LogWrite(LOOT__DEBUG, 0, "Loot", "--Loading LootTables...");
LootTable* table = 0;
// Load loottable from DB
while(result.Next()) {
int32 id = result.GetInt32Str("id");
table = new LootTable;
table->name = result.GetStringStr("name");
table->mincoin = result.GetInt32Str("mincoin");
table->maxcoin = result.GetInt32Str("maxcoin");
table->maxlootitems = result.GetInt16Str("maxlootitems");
table->lootdrop_probability = result.GetFloatStr("lootdrop_probability");
table->coin_probability = result.GetFloatStr("coin_probability");
zone->AddLootTable(id, table);
LogWrite(LOOT__DEBUG, 5, "Loot", "---Loading LootTable '%s' (id: %u)", table->name.c_str(), id);
LogWrite(LOOT__DEBUG, 5, "Loot", "---min_coin: %u, max_coin: %u, max_items: %i, prob: %.2f, coin_prob: %.2f", table->mincoin, table->maxcoin, table->maxlootitems, table->lootdrop_probability, table->coin_probability);
count++;
}
LogWrite(LOOT__DEBUG, 0, "Loot", "--Loaded %u loot table%s.", count, count == 1 ? "" : "s");
}
// Now, load Loot Drops for configured loot tables
if (database_new.Select(&result, "SELECT loot_table_id, item_id, item_charges, equip_item, probability, no_drop_quest_completed FROM lootdrop")) {
count = 0;
LogWrite(LOOT__DEBUG, 0, "Loot", "--Loading LootDrops...");
LootDrop* drop = 0;
while(result.Next()) {
int32 id = result.GetInt32Str("loot_table_id");
drop = new LootDrop;
drop->item_id = result.GetInt32Str("item_id");
drop->item_charges = result.GetInt16Str("item_charges");
drop->equip_item = (result.GetInt8Str("equip_item") == 1);
drop->probability = result.GetFloatStr("probability");
drop->no_drop_quest_completed_id = result.GetInt32Str("no_drop_quest_completed");
zone->AddLootDrop(id, drop);
LogWrite(LOOT__DEBUG, 5, "Loot", "---Loading LootDrop item_id %u (tableID: %u", drop->item_id, id);
LogWrite(LOOT__DEBUG, 5, "Loot", "---charges: %i, equip_item: %i, prob: %.2f", drop->item_charges, drop->equip_item, drop->probability);
count++;
}
LogWrite(LOOT__DEBUG, 0, "Loot", "--Loaded %u loot drop%s.", count, count == 1 ? "" : "s");
}
// Finally, load loot tables into spawns that are set to use these loot tables
if (database_new.Select(&result, "SELECT spawn_id, loottable_id FROM spawn_loot")) {
count = 0;
LogWrite(LOOT__DEBUG, 0, "Loot", "--Assigning loot table(s) to spawn(s)...");
while(result.Next()) {
int32 spawn_id = result.GetInt32Str("spawn_id");
int32 table_id = result.GetInt32Str("loottable_id");
zone->AddSpawnLootList(spawn_id, table_id);
LogWrite(LOOT__DEBUG, 5, "Loot", "---Adding loot table %u to spawn %u", table_id, spawn_id);
count++;
}
LogWrite(LOOT__DEBUG, 0, "Loot", "--Loaded %u spawn loot list%s.", count, count == 1 ? "" : "s");
}
// Load global loot lists
LoadGlobalLoot(zone);
}
void WorldDatabase::LoadGlobalLoot(ZoneServer* zone) {
LogWrite(LOOT__INFO, 0, "Loot", "-Loading Global loot data...");
DatabaseResult result;
int32 count = 0;
if (database_new.Select(&result, "SELECT type, loot_table, value1, value2, value3, value4 FROM loot_global")) {
while(result.Next()) {
const char* type = result.GetStringStr("type");
int32 table_id = result.GetInt32Str("loot_table");
if (strcmp(type, "Level") == 0) {
GlobalLoot* loot = new GlobalLoot();
loot->minLevel = result.GetInt8Str("value1");
loot->maxLevel = result.GetInt8Str("value2");
loot->table_id = table_id;
loot->loot_tier = result.GetInt32Str("value4");
if (loot->minLevel > loot->maxLevel)
loot->maxLevel = loot->minLevel;
zone->AddLevelLootList(loot);
LogWrite(LOOT__DEBUG, 5, "Loot", "---Loading Level %i loot table (id: %u)", loot->minLevel, table_id);
LogWrite(LOOT__DEBUG, 5, "Loot", "---minlevel: %i, maxlevel: %i", loot->minLevel, loot->maxLevel);
}
else if (strcmp(type, "Racial") == 0) {
GlobalLoot* loot = new GlobalLoot();
int16 race_id = result.GetInt16Str("value1");
loot->minLevel = result.GetInt8Str("value2");
loot->maxLevel = result.GetInt8Str("value3");
loot->table_id = table_id;
loot->loot_tier = result.GetInt32Str("value4");
if (loot->minLevel > loot->maxLevel)
loot->maxLevel = loot->minLevel;
zone->AddRacialLootList(race_id, loot);
LogWrite(LOOT__DEBUG, 5, "Loot", "---Loading Racial %i loot table (id: %u)", race_id, table_id);
LogWrite(LOOT__DEBUG, 5, "Loot", "---minlevel: %i, maxlevel: %i", loot->minLevel, loot->maxLevel);
}
else if (strcmp(type, "Zone") == 0) {
GlobalLoot* loot = new GlobalLoot();
int32 zoneID = result.GetInt32Str("value1");
loot->minLevel = result.GetInt8Str("value2");
loot->maxLevel = result.GetInt8Str("value3");
loot->table_id = table_id;
loot->loot_tier = result.GetInt32Str("value4");
if (loot->minLevel > loot->maxLevel)
loot->maxLevel = loot->minLevel;
zone->AddZoneLootList(zoneID, loot);
LogWrite(LOOT__DEBUG, 5, "Loot", "---Loading Zone %i loot table (id: %u)", zoneID, table_id);
LogWrite(LOOT__DEBUG, 5, "Loot", "---minlevel: %i, maxlevel: %i", loot->minLevel, loot->maxLevel);
}
count++;
}
LogWrite(LOOT__DEBUG, 4, "Loot", "--Loaded %u Global loot list%s.", count, count == 1 ? "" : "s");
}
}
bool WorldDatabase::LoadSpawnLoot(ZoneServer* zone, Spawn* spawn)
{
if (!spawn->GetDatabaseID())
return false;
DatabaseResult result;
int32 count = 0;
zone->ClearSpawnLootList(spawn->GetDatabaseID());
// Finally, load loot tables into spawns that are set to use these loot tables
if (database_new.Select(&result, "SELECT spawn_id, loottable_id FROM spawn_loot where spawn_id=%u",spawn->GetDatabaseID())) {
count = 0;
LogWrite(LOOT__DEBUG, 0, "Loot", "--Assigning loot table(s) to spawn(s)...");
while (result.Next()) {
int32 spawn_id = result.GetInt32Str("spawn_id");
int32 table_id = result.GetInt32Str("loottable_id");
zone->AddSpawnLootList(spawn_id, table_id);
LogWrite(LOOT__DEBUG, 5, "Loot", "---Adding loot table %u to spawn %u", table_id, spawn_id);
count++;
}
LogWrite(LOOT__DEBUG, 4, "Loot", "--Loaded %u spawn loot list%s.", count, count == 1 ? "" : "s");
return true;
}
return false;
}
void WorldDatabase::AddLootTableToSpawn(Spawn* spawn, int32 loottable_id) {
Query query;
query.RunQuery2(Q_INSERT, "insert into spawn_loot set spawn_id=%u,loottable_id=%u", spawn->GetDatabaseID(), loottable_id);
}
bool WorldDatabase::RemoveSpawnLootTable(Spawn* spawn, int32 loottable_id) {
Query query;
if (loottable_id)
{
string delete_char = string("delete from spawn_loot where spawn_id=%i and loottable_id=%i");
query.RunQuery2(Q_DELETE, delete_char.c_str(), spawn->GetDatabaseID(), loottable_id);
}
else
{
string delete_char = string("delete from spawn_loot where spawn_id=%i");
query.RunQuery2(Q_DELETE, delete_char.c_str(), spawn->GetDatabaseID());
}
if (!query.GetAffectedRows())
{
//No error just in case ppl try doing stupid stuff
return false;
}
return true;
}

View File

@ -0,0 +1,153 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <string.h>
#include "Languages.h"
Language::Language(){
id = 0;
memset(name, 0, sizeof(name));
save_needed = false;
}
Language::Language(Language* language){
id = language->id;
strncpy(name, language->GetName(), sizeof(name));
save_needed = language->save_needed;
}
MasterLanguagesList::MasterLanguagesList(){
}
MasterLanguagesList::~MasterLanguagesList(){
Clear();
}
// don't bother calling this beyond its deconstructor its not thread-safe
void MasterLanguagesList::Clear(){
list<Language*>::iterator itr;
Language* language = 0;
for(itr = languages_list.begin(); itr != languages_list.end(); itr++){
language = *itr;
safe_delete(language);
}
languages_list.clear();
}
int32 MasterLanguagesList::Size(){
return languages_list.size();
}
void MasterLanguagesList::AddLanguage(Language* language){
assert(language);
languages_list.push_back(language);
}
Language* MasterLanguagesList::GetLanguage(int32 id){
list<Language*>::iterator itr;
Language* language = 0;
Language* ret = 0;
for(itr = languages_list.begin(); itr != languages_list.end(); itr++){
language = *itr;
if(language->GetID() == id){
ret = language;
break;
}
}
return ret;
}
Language* MasterLanguagesList::GetLanguageByName(const char* name){
list<Language*>::iterator itr;
Language* language = 0;
Language* ret = 0;
for(itr = languages_list.begin(); itr != languages_list.end(); itr++){
language = *itr;
if(!language)
continue;
if(!strncmp(language->GetName(), name, strlen(name))){
ret = language;
break;
}
}
return ret;
}
list<Language*>* MasterLanguagesList::GetAllLanguages(){
return &languages_list;
}
PlayerLanguagesList::PlayerLanguagesList(){
}
PlayerLanguagesList::~PlayerLanguagesList(){
}
void PlayerLanguagesList::Clear() {
list<Language*>::iterator itr;
Language* language = 0;
Language* ret = 0;
for(itr = player_languages_list.begin(); itr != player_languages_list.end(); itr++){
language = *itr;
safe_delete(language);
}
player_languages_list.clear();
}
void PlayerLanguagesList::Add(Language* language){
player_languages_list.push_back(language);
}
Language* PlayerLanguagesList::GetLanguage(int32 id){
list<Language*>::iterator itr;
Language* language = 0;
Language* ret = 0;
for(itr = player_languages_list.begin(); itr != player_languages_list.end(); itr++){
language = *itr;
if(language->GetID() == id){
ret = language;
break;
}
}
return ret;
}
Language* PlayerLanguagesList::GetLanguageByName(const char* name){
list<Language*>::iterator itr;
Language* language = 0;
Language* ret = 0;
for(itr = player_languages_list.begin(); itr != player_languages_list.end(); itr++){
language = *itr;
if(!language)
continue;
if(!strncmp(language->GetName(), name, strlen(name))){
ret = language;
break;
}
}
return ret;
}
list<Language*>* PlayerLanguagesList::GetAllLanguages(){
return &player_languages_list;
}

View File

@ -0,0 +1,76 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LANGUAGES_H_
#define LANGUAGES_H_
#include <string>
#include <list>
#include "../common/types.hpp"
using namespace std;
class Language {
public:
Language();
Language(Language* language);
void SetID(int32 id) {this->id = id;}
void SetName(const char *name) {strncpy(this->name, name, sizeof(this->name));}
void SetSaveNeeded(bool save_needed) {this->save_needed = save_needed;}
int32 GetID() {return id;}
const char* GetName() {return name;}
bool GetSaveNeeded() {return save_needed;}
private:
int32 id;
char name[50];
bool save_needed;
};
class MasterLanguagesList {
public:
MasterLanguagesList();
~MasterLanguagesList();
void Clear();
int32 Size();
void AddLanguage(Language* language);
Language* GetLanguage(int32 id);
Language* GetLanguageByName(const char* name);
list<Language*>* GetAllLanguages();
private:
list<Language*> languages_list;
};
class PlayerLanguagesList {
public:
PlayerLanguagesList();
~PlayerLanguagesList();
void Clear();
void Add(Language* language);
Language* GetLanguage(int32 id);
Language* GetLanguageByName(const char* name);
list<Language*>* GetAllLanguages();
private:
list<Language*> player_languages_list;
};
#endif

View File

@ -0,0 +1,748 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "../common/debug.hpp"
#include "../common/log.hpp"
#include <iostream>
using namespace std;
#include <string.h>
#include <stdio.h>
#include <iomanip>
using namespace std;
#include <stdlib.h>
#include "../common/version.h"
#include "../common/GlobalHeaders.h"
#include "../common/sha512.h"
#ifdef WIN32
#include <process.h>
#include <WinSock2.h>
#include <Ws2tcpip.h>
#include <windows.h>
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
#else // Pyro: fix for linux
#include <sys/socket.h>
#ifdef FREEBSD //Timothy Whitman - January 7, 2003
#include <sys/types.h>
#endif
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include "../common/unix.h"
#define SOCKET_ERROR -1
#define INVALID_SOCKET -1
extern int errno;
#endif
#include "../common/servertalk.h"
#include "LoginServer.h"
#include "../common/packet/packet_dump.hpp"
#include "net.h"
#include "zoneserver.h"
#include "WorldDatabase.hpp"
#include "Variables.h"
#include "World.h"
#include "../common/config_reader.hpp"
#include "Rules/Rules.h"
#include "Web/PeerManager.h"
#include "Web/HTTPSClientPool.h"
extern sint32 numzones;
extern sint32 numclients;
extern NetConnection net;
extern LoginServer loginserver;
extern WorldDatabase database;
extern ZoneAuth zone_auth;
extern Variables variables;
extern ZoneList zone_list;
extern ClientList client_list;
extern volatile bool RunLoops;
volatile bool LoginLoopRunning = false;
extern ConfigReader configReader;
extern RuleManager rule_manager;
extern PeerManager peer_manager;
extern HTTPSClientPool peer_https_pool;
bool AttemptingConnect = false;
LoginServer::LoginServer(const char* iAddress, int16 iPort) {
LoginServerIP = ResolveIP(iAddress);
LoginServerPort = iPort;
statusupdate_timer = new Timer(LoginServer_StatusUpdateInterval);
tcpc = new TCPConnection(false);
pTryReconnect = true;
minLockedStatus = 100;
maxPlayers = -1;
minGameFullStatus = 100;
last_checked_time = 0;
zone_updates = 0;
loginEquip_updates = 0;
}
LoginServer::~LoginServer() {
delete statusupdate_timer;
delete tcpc;
}
void LoginServer::SendImmediateEquipmentUpdatesForChar(int32 char_id) {
LogWrite(WORLD__DEBUG, 5, "World", "Sending login equipment updates for char_id: %u", char_id);
int16 count = 0;
if(!loginEquip_updates)
loginEquip_updates = database.GetEquipmentUpdates(char_id);
if(loginEquip_updates && loginEquip_updates->size() > 0)
{
map<int32, LoginEquipmentUpdate> send_map;
int32 size = 0;
MutexMap<int32, LoginEquipmentUpdate>::iterator itr = loginEquip_updates->begin();
while(itr.Next())
{
send_map[itr->first] = itr->second;
size += sizeof(EquipmentUpdate_Struct);
loginEquip_updates->erase(itr->first);
count++;
}
ServerPacket* outpack = new ServerPacket(ServerOP_LoginEquipment, size + sizeof(EquipmentUpdateList_Struct)+5);
EquipmentUpdateList_Struct* updates = (EquipmentUpdateList_Struct*)outpack->pBuffer;
updates->total_updates = count;
int32 pos = sizeof(EquipmentUpdateList_Struct);
map<int32, LoginEquipmentUpdate>::iterator send_itr;
for(send_itr = send_map.begin(); send_itr != send_map.end(); send_itr++)
{
EquipmentUpdate_Struct* update = (EquipmentUpdate_Struct*)(outpack->pBuffer + pos);
update->id = send_itr->first;
update->world_char_id = send_itr->second.world_char_id;
update->equip_type = send_itr->second.equip_type;
update->red = send_itr->second.red;
update->green = send_itr->second.green;
update->blue = send_itr->second.blue;
update->highlight_red = send_itr->second.red;
update->highlight_green = send_itr->second.green;
update->highlight_blue = send_itr->second.blue;
update->slot = send_itr->second.slot;
pos += sizeof(EquipmentUpdate_Struct);
}
SendPacket(outpack);
outpack->Deflate();
safe_delete(outpack);
}
if(loginEquip_updates && count)
loginEquip_updates->clear();
if(loginEquip_updates && loginEquip_updates->size() == 0)
{
database.UpdateLoginEquipment();
safe_delete(loginEquip_updates);
}
}
bool LoginServer::Process() {
if(last_checked_time > Timer::GetCurrentTime2())
return true;
last_checked_time = Timer::GetCurrentTime2() + 50;
bool ret = true;
if (statusupdate_timer->Check()) {
this->SendStatus();
}
/************ Get all packets from packet manager out queue and process them ************/
ServerPacket *pack = 0;
while((pack = tcpc->PopPacket()))
{
switch(pack->opcode)
{
case ServerOP_LSFatalError:
{
LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_LSFatalError", pack->opcode, pack->opcode);
LogWrite(WORLD__ERROR, 0, "World", "Login Server returned a fatal error: %s\n", pack->pBuffer);
tcpc->Disconnect();
ret = false;
//net.ReadLoginINI(); // can't properly support with command line args now
break;
}
case ServerOP_CharTimeStamp:
{
LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_CharTimeStamp", pack->opcode, pack->opcode);
if(pack->size != sizeof(CharacterTimeStamp_Struct))
break;
CharacterTimeStamp_Struct* cts = (CharacterTimeStamp_Struct*) pack->pBuffer;
// determine if the character exists and retrieve its latest timestamp from the world server
bool char_exist = false;
//int32 character_timestamp = database.GetCharacterTimeStamp(cts->char_id,cts->account_id,&char_exist);
if(!char_exist)
{
//Character doesn't exist, get rid of it
SendDeleteCharacter ( cts );
break;
}
break;
}
// Push Character Select "item appearances" to login_equipment table
case ServerOP_LoginEquipment:{
LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_LoginEquipment", pack->opcode, pack->opcode);
LogWrite(MISC__TODO, 0, "TODO", "Implement map<character id <map<slot id, updatestruct> > method to update Login.\n%s, %s, %i", __FILE__, __FUNCTION__, __LINE__);
if( pack->size == sizeof(EquipmentUpdateRequest_Struct) )
{
int16 max = ((EquipmentUpdateRequest_Struct*)pack->pBuffer)->max_per_batch;
int16 count = 0;
if(!loginEquip_updates)
loginEquip_updates = database.GetEquipmentUpdates();
if(loginEquip_updates && loginEquip_updates->size() > 0)
{
map<int32, LoginEquipmentUpdate> send_map;
int32 size = 0;
MutexMap<int32, LoginEquipmentUpdate>::iterator itr = loginEquip_updates->begin();
while(itr.Next() && count < max)
{
send_map[itr->first] = itr->second;
size += sizeof(EquipmentUpdate_Struct);
loginEquip_updates->erase(itr->first);
count++;
}
ServerPacket* outpack = new ServerPacket(ServerOP_LoginEquipment, size + sizeof(EquipmentUpdateList_Struct)+5);
EquipmentUpdateList_Struct* updates = (EquipmentUpdateList_Struct*)outpack->pBuffer;
updates->total_updates = count;
int32 pos = sizeof(EquipmentUpdateList_Struct);
map<int32, LoginEquipmentUpdate>::iterator send_itr;
for(send_itr = send_map.begin(); send_itr != send_map.end(); send_itr++)
{
EquipmentUpdate_Struct* update = (EquipmentUpdate_Struct*)(outpack->pBuffer + pos);
update->id = send_itr->first;
update->world_char_id = send_itr->second.world_char_id;
update->equip_type = send_itr->second.equip_type;
update->red = send_itr->second.red;
update->green = send_itr->second.green;
update->blue = send_itr->second.blue;
update->highlight_red = send_itr->second.red;
update->highlight_green = send_itr->second.green;
update->highlight_blue = send_itr->second.blue;
update->slot = send_itr->second.slot;
pos += sizeof(EquipmentUpdate_Struct);
}
SendPacket(outpack);
outpack->Deflate();
safe_delete(outpack);
}
if(loginEquip_updates && count < max)
loginEquip_updates->clear();
if(loginEquip_updates && loginEquip_updates->size() == 0)
{
database.UpdateLoginEquipment();
safe_delete(loginEquip_updates);
}
}
break;
}
case ServerOP_ZoneUpdates:{
LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_ZoneUpdates", pack->opcode, pack->opcode);
if(pack->size == sizeof(ZoneUpdateRequest_Struct)){
int16 max = ((ZoneUpdateRequest_Struct*)pack->pBuffer)->max_per_batch;
int16 count = 0;
if(!zone_updates)
zone_updates = database.GetZoneUpdates();
if(zone_updates && zone_updates->size() > 0){
map<int32, LoginZoneUpdate> send_map;
int32 size = 0;
MutexMap<int32, LoginZoneUpdate>::iterator itr = zone_updates->begin();
while(itr.Next() && count < max){
send_map[itr->first] = itr->second;
size += sizeof(ZoneUpdate_Struct) + itr->second.name.length() + itr->second.description.length();
zone_updates->erase(itr->first);
count++;
}
ServerPacket* outpack = new ServerPacket(ServerOP_ZoneUpdates, size + sizeof(ZoneUpdateList_Struct)+5);
ZoneUpdateList_Struct* updates = (ZoneUpdateList_Struct*)outpack->pBuffer;
updates->total_updates = count;
int32 pos = sizeof(ZoneUpdateList_Struct);
map<int32, LoginZoneUpdate>::iterator send_itr;
for(send_itr = send_map.begin(); send_itr != send_map.end(); send_itr++){
ZoneUpdate_Struct* update = (ZoneUpdate_Struct*)(outpack->pBuffer + pos);
update->zone_id = send_itr->first;
update->zone_name_length = send_itr->second.name.length();
update->zone_desc_length = send_itr->second.description.length();
strcpy(update->data, send_itr->second.name.c_str());
strcpy(update->data + send_itr->second.name.length(), send_itr->second.description.c_str());
pos += sizeof(ZoneUpdate_Struct) + send_itr->second.name.length() + send_itr->second.description.length();
}
SendPacket(outpack);
outpack->Deflate();
safe_delete(outpack);
}
if(zone_updates && count < max)
zone_updates->clear();
if(zone_updates && zone_updates->size() == 0){
database.UpdateLoginZones();
safe_delete(zone_updates);
}
}
break;
}
case ServerOP_CharacterCreate:{
LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_CharacterCreate", pack->opcode, pack->opcode);
int16 version = 1;
if(pack->pBuffer[0] > 0)
memcpy(&version, pack->pBuffer, sizeof(int16));
//DumpPacket(pack->pBuffer,pack->size);
PacketStruct* packet = configReader.getStruct("CreateCharacter", version);
int8 resp = 0;
int32 acct_id = 0;
int32 char_id = 0;
// have to load packet to clear the buffer
if(packet && packet->LoadPacketData(pack->pBuffer+sizeof(int16),pack->size - sizeof(int16), version <= 561 ? false : true)){
if(net.world_locked) {
resp = NOSERVERSAVAIL_REPLY; // no new characters when locked
}
else {
EQ2_16BitString name = packet->getType_EQ2_16BitString_ByName("name");
resp = database.CheckNameFilter(name.data.c_str());
acct_id = packet->getType_int32_ByName("account_id");
LogWrite(WORLD__DEBUG, 0, "World", "Response: %i", (int)resp);
sint16 lowestStatus = database.GetLowestCharacterAdminStatus(acct_id);
if(lowestStatus == -2)
resp = UNKNOWNERROR_REPLY2;
else if(resp == CREATESUCCESS_REPLY)
char_id = database.SaveCharacter(packet, acct_id);
}
}
else{
LogWrite(WORLD__ERROR, 0, "World", "Invalid creation request!");
resp = UNKNOWNERROR_REPLY;
}
// send name filter response data back to the login server
SendFilterNameResponse ( resp , acct_id , char_id );
safe_delete(packet);
break;
}
case ServerOP_BasicCharUpdate: {
LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_BasicCharUpdate", pack->opcode, pack->opcode);
if(pack->size != sizeof(CharDataUpdate_Struct))
break;
CharDataUpdate_Struct* cdu = (CharDataUpdate_Struct*) pack->pBuffer;
switch(cdu->update_field)
{
case DELETE_UPDATE_FLAG:
{
LogWrite(WORLD__DEBUG, 0, "World", "Delete character request: %i %i",cdu->account_id,cdu->char_id );
database.DeleteCharacter(cdu->account_id,cdu->char_id);
break;
}
}
break;
}
case ServerOP_UsertoWorldReq:{
LogWrite(OPCODE__DEBUG, 0, "Opcode", "Opcode 0x%X (%i): ServerOP_UsertoWorldReq", pack->opcode, pack->opcode);
UsertoWorldRequest_Struct* utwr = (UsertoWorldRequest_Struct*) pack->pBuffer;
/*int32 id = database.GetAccountIDFromLSID(utwr->lsaccountid);
sint16 status = database.CheckStatus(id);
*/
int32 access_key = 0;
ZoneChangeDetails details;
std::string name = database.loadCharacterFromLogin(&details, utwr->char_id, utwr->lsaccountid);
// if it is a accepted login, we add the zone auth request
access_key = DetermineCharacterLoginRequest ( utwr, &details, name);
if ( access_key != 0 )
{
zone_auth.PurgeInactiveAuth();
char* characterName = database.GetCharacterName( utwr->char_id );
if(characterName != 0){
ZoneAuthRequest* zar = new ZoneAuthRequest(utwr->lsaccountid,characterName,access_key);
zar->setFirstLogin ( true );
zone_auth.AddAuth(zar);
safe_delete_array(characterName);
}
}
break;
}
case ServerOP_ResetDatabase:{
LogWrite(OPCODE__DEBUG, 1, "Opcode", "Opcode 0x%X (%i): ServerOP_ResetDatabase", pack->opcode, pack->opcode);
database.ResetDatabase();
break;
}
default:
{
LogWrite(WORLD__ERROR, 0, "World", "Unhandled opcode: %i", pack->opcode);
DumpPacket(pack);
}
}
safe_delete(pack);
// break out if ret is now false
if (!ret)
break;
}
return ret;
}
// this should always be called in a new thread
#ifdef WIN32
void AutoInitLoginServer(void *tmp) {
#else
void *AutoInitLoginServer(void *tmp) {
#endif
if (loginserver.GetState() == TCPS_Ready) {
InitLoginServer();
}
#ifndef WIN32
return 0;
#endif
}
bool InitLoginServer() {
if (loginserver.GetState() != TCPS_Ready) {
LogWrite(WORLD__ERROR, 0, "World", "InitLoginServer() while already attempting connect.");
return false;
}
if (!net.LoginServerInfo) {
LogWrite(WORLD__ERROR, 0, "World", "Login server info not loaded.");
return false;
}
AttemptingConnect = true;
int16 port;
char* address = net.GetLoginInfo(&port);
LogWrite(WORLD__INFO, 0, "World", "InitLoginServer() attempt connect to %s on port %u.", address, port);
loginserver.Connect(address, port);
return true;
}
void LoginServer::InitLoginServerVariables()
{
minLockedStatus = rule_manager.GetGlobalRule(R_World, ServerLockedOverrideStatus)->GetSInt16();
maxPlayers = rule_manager.GetGlobalRule(R_World, MaxPlayers)->GetSInt16();
minGameFullStatus = rule_manager.GetGlobalRule(R_World, MaxPlayersOverrideStatus)->GetSInt16();
}
bool LoginServer::Connect(const char* iAddress, int16 iPort) {
if(!pTryReconnect)
return false;
char errbuf[TCPConnection_ErrorBufferSize];
memset(errbuf, 0, TCPConnection_ErrorBufferSize);
if (iAddress == 0) {
LogWrite(WORLD__ERROR, 0, "World", "LoginServer::Connect: address == 0");
return false;
}
else {
if ((LoginServerIP = ResolveIP(iAddress, errbuf)) == 0) {
LogWrite(WORLD__ERROR, 0, "World", "LoginServer::Connect: Resolving IP address: '%s'", errbuf);
return false;
}
}
if (iPort != 0)
LoginServerPort = iPort;
if (LoginServerIP == 0 || LoginServerPort == 0) {
LogWrite(WORLD__ERROR, 0, "World", "LoginServer::Connect: Connect info incomplete, cannot connect");
return false;
}
if (tcpc->Connect(LoginServerIP, LoginServerPort, errbuf)) {
LogWrite(WORLD__INFO, 0, "World", "Connected to LoginServer: %s: %i", iAddress, LoginServerPort);
SendInfo();
SendStatus();
return true;
}
else {
LogWrite(WORLD__ERROR, 0, "World", "LoginServer::Connect: '%s'", errbuf);
return false;
}
}
void LoginServer::GetLatestTables(){
ServerPacket* pack = new ServerPacket(ServerOP_GetLatestTables, sizeof(GetLatestTables_Struct));
GetLatestTables_Struct* data = (GetLatestTables_Struct*)pack->pBuffer;
data->table_version = CURRENT_DATABASE_MAJORVERSION*100 + CURRENT_DATABASE_MINORVERSION;
data->data_version = CURRENT_DATABASE_MAJORVERSION*100 + CURRENT_DATABASE_MINORVERSION;
SendPacket(pack);
delete pack;
}
void LoginServer::SendInfo() {
ServerPacket* pack = new ServerPacket;
pack->opcode = ServerOP_LSInfo;
pack->size = sizeof(ServerLSInfo_Struct);
pack->pBuffer = new uchar[pack->size];
memset(pack->pBuffer, 0, pack->size);
ServerLSInfo_Struct* lsi = (ServerLSInfo_Struct*) pack->pBuffer;
strcpy(lsi->protocolversion, EQEMU_PROTOCOL_VERSION);
strcpy(lsi->serverversion, CURRENT_VERSION);
strcpy(lsi->name, net.GetWorldName());
strcpy(lsi->account, net.GetWorldAccount());
lsi->dbversion = CURRENT_DATABASE_MAJORVERSION*100 + CURRENT_DATABASE_MINORVERSION;
#ifdef _DEBUG
lsi->servertype = 4;
#endif
string passwdSha512 = sha512(net.GetWorldPassword());
memcpy(lsi->password, (char*)passwdSha512.c_str(), passwdSha512.length());
strcpy(lsi->address, net.GetWorldAddress());
SendPacket(pack);
delete pack;
}
void LoginServer::SendStatus() {
statusupdate_timer->Start();
ServerPacket* pack = new ServerPacket;
pack->opcode = ServerOP_LSStatus;
pack->size = sizeof(ServerLSStatus_Struct);
pack->pBuffer = new uchar[pack->size];
memset(pack->pBuffer, 0, pack->size);
ServerLSStatus_Struct* lss = (ServerLSStatus_Struct*) pack->pBuffer;
if (net.world_locked)
lss->status = -2;
else if(loginserver.maxPlayers > -1 && numclients >= loginserver.maxPlayers)
lss->status = -3;
else
lss->status = 1;
lss->num_zones = numzones;
lss->num_players = numclients;
lss->world_max_level = rule_manager.GetGlobalRule(R_Player, MaxLevel)->GetInt8();
SendPacket(pack);
delete pack;
}
void LoginServer::SendDeleteCharacter ( CharacterTimeStamp_Struct* cts ) {
ServerPacket* outpack = new ServerPacket(ServerOP_BasicCharUpdate, sizeof(CharDataUpdate_Struct));
CharDataUpdate_Struct* cdu = (CharDataUpdate_Struct*)outpack->pBuffer;
cdu->account_id = cts->account_id;
cdu->char_id = cts->char_id;
cdu->update_field = DELETE_UPDATE_FLAG;
cdu->update_data = 1;
SendPacket(outpack);
safe_delete(outpack);
}
void LoginServer::SendFilterNameResponse ( int8 resp, int32 acct_id , int32 char_id ) {
ServerPacket* outpack = new ServerPacket(ServerOP_CharacterCreate, sizeof(WorldCharNameFilterResponse_Struct));
WorldCharNameFilterResponse_Struct* wcfr = (WorldCharNameFilterResponse_Struct*)outpack->pBuffer;
wcfr->response = resp;
wcfr->account_id = acct_id;
wcfr->char_id = char_id;
SendPacket(outpack);
safe_delete(outpack);
}
int32 LoginServer::DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* utwr, ZoneChangeDetails* details, std::string name) {
LogWrite(LOGIN__TRACE, 9, "Login", "Enter: %s", __FUNCTION__);
int32 timestamp = Timer::GetUnixTimeStamp();
int32 key = static_cast<unsigned int>(MakeRandomFloat(0.01,1.0) * UINT32_MAX);
int8 response = 0;
sint16 lowestStatus = database.GetLowestCharacterAdminStatus( utwr->lsaccountid );
sint16 status = 0;
if(lowestStatus == -2)
status = -1;
else
status = database.GetCharacterAdminStatus ( utwr->lsaccountid , utwr->char_id );
if(status < 100 && zone_list.ClientConnected(utwr->lsaccountid))
status = -9;
if(status < 0){
LogWrite(WORLD__ERROR, 0, "World", "Login Rejected based on PLAY_ERROR (UserStatus) (MinStatus: %i), UserStatus: %i, CharID: %i",loginserver.minLockedStatus,status,utwr->char_id );
switch(status){
case -10:
response = PLAY_ERROR_CHAR_NOT_LOADED;
break;
case -9:
response = 0;//PLAY_ERROR_ACCOUNT_IN_USE;
break;
case -8:
response = PLAY_ERROR_LOADING_ERROR;
break;
case -1:
response = PLAY_ERROR_ACCOUNT_BANNED;
break;
default:
response = PLAY_ERROR_PROBLEM;
}
}
else if(net.world_locked == true){
LogWrite(WORLD__INFO, 0, "World", "Login Lock Check (MinStatus: %i):, UserStatus: %i, CharID: %i",loginserver.minLockedStatus,status,utwr->char_id );
// has high enough status, allow it
if(status >= loginserver.minLockedStatus)
response = 1;
}
else if(loginserver.maxPlayers > -1 && ((sint16)client_list.Count()) >= loginserver.maxPlayers)
{
LogWrite(WORLD__INFO, 0, "World", "Login GameFull Check (MinStatus: %i):, UserStatus: %i, CharID: %i",loginserver.minGameFullStatus,status,utwr->char_id );
// has high enough status, allow it
if(status >= loginserver.minGameFullStatus)
{
response = 1;
}
else
response = -3; // server full response is -3
}
else
response = 1;
bool attemptedPeer = false;
if(response == 1 && details->peerId.size() > 0 && details->peerId != "self" && name.size() > 0) {
boost::property_tree::ptree root;
root.put("account_id", utwr->lsaccountid);
root.put("character_name", std::string(name));
root.put("character_id", std::to_string(utwr->char_id));
root.put("zone_id", std::to_string(details->zoneId));
root.put("instance_id", std::to_string(details->instanceId));
root.put("login_key", std::to_string(key));
root.put("client_ip", std::string(utwr->ip_address));
root.put("world_id", std::to_string(utwr->worldid));
root.put("from_id", std::to_string(utwr->FromID));
root.put("first_login", true);
std::ostringstream jsonStream;
boost::property_tree::write_json(jsonStream, root);
std::string jsonPayload = jsonStream.str();
LogWrite(PEERING__INFO, 0, "Peering", "%s: Sending AddCharAuth for %s to peer %s:%u for existing zone %s", __FUNCTION__, name.c_str(), details->peerWebAddress.c_str(), details->peerWebPort, details->zoneName.c_str());
attemptedPeer = true;
peer_https_pool.sendPostRequestToPeerAsync(details->peerId, details->peerWebAddress, std::to_string(details->peerWebPort), "/addcharauth", jsonPayload);
}
else if(response == 1 && details->peerId == "") {
std::shared_ptr<Peer> peer = peer_manager.getHealthyPeerWithLeastClients();
if(peer != nullptr) {
boost::property_tree::ptree root;
char* characterName = database.GetCharacterName( utwr->char_id );
if(!characterName) {
LogWrite(PEERING__ERROR, 0, "Peering", "%s: AddCharAuth failed to identify character name for char id %u to peer %s:%u", __FUNCTION__, utwr->char_id, peer->webAddr.c_str(), peer->webPort);
}
else {
root.put("account_id", utwr->lsaccountid);
root.put("character_name", std::string(characterName));
root.put("character_id", std::to_string(utwr->char_id));
root.put("zone_id", std::to_string(details->zoneId));
root.put("instance_id", std::to_string(details->instanceId));
root.put("login_key", std::to_string(key));
root.put("client_ip", std::string(utwr->ip_address));
root.put("world_id", std::to_string(utwr->worldid));
root.put("from_id", std::to_string(utwr->FromID));
root.put("first_login", true);
std::ostringstream jsonStream;
boost::property_tree::write_json(jsonStream, root);
std::string jsonPayload = jsonStream.str();
LogWrite(PEERING__INFO, 0, "Peering", "%s: Sending AddCharAuth for %s to peer %s:%u for new zone %s", __FUNCTION__, characterName, peer->webAddr.c_str(), peer->webPort, details->zoneName.c_str());
attemptedPeer = true;
peer_https_pool.sendPostRequestToPeerAsync(peer->id, peer->webAddr, std::to_string(peer->webPort), "/addcharauth", jsonPayload);
}
}
else if(peer_manager.hasPeers()) {
LogWrite(PEERING__WARNING, 0, "Peering", "%s: AddCharAuth failed to find healthy peer for char id %u", __FUNCTION__, utwr->char_id);
}
}
/*sint32 x = database.CommandRequirement("$MAXCLIENTS");
if( (sint32)numplayers >= x && x != -1 && x != 255 && status < 80)
utwrs->response = -3;
if(status == -1)
utwrs->response = -1;
if(status == -2)
utwrs->response = -2;
*/
//printf("Response is %i for %i\n",utwrs->response,id);struct sockaddr_in sa;
if(!attemptedPeer) {
SendCharApprovedLogin(response, "", "", std::string(utwr->ip_address), 0, utwr->lsaccountid, utwr->char_id, key, utwr->worldid, utwr->FromID);
}
LogWrite(LOGIN__TRACE, 9, "Login", "Exit: %s with timestamp=%u", __FUNCTION__, timestamp);
// depending on the response determined above, this could return 0 (for failure)
return (attemptedPeer) ? 0 : key;
}
void LoginServer::SendCharApprovedLogin(int8 response, std::string peerAddress, std::string peerInternalAddress, std::string clientIP, int16 peerPort, int32 account_id, int32 char_id, int32 key, int32 world_id, int32 from_id) {
ServerPacket* outpack = new ServerPacket;
outpack->opcode = ServerOP_UsertoWorldResp;
outpack->size = sizeof(UsertoWorldResponse_Struct);
outpack->pBuffer = new uchar[outpack->size];
memset(outpack->pBuffer, 0, outpack->size);
UsertoWorldResponse_Struct* utwrs = (UsertoWorldResponse_Struct*) outpack->pBuffer;
utwrs->response = response;
utwrs->lsaccountid = account_id;
utwrs->char_id = char_id;
utwrs->ToID = from_id;
utwrs->access_key = key;
int32 ipv4addr = 0;
int result = 0;
#ifdef WIN32
struct sockaddr_in myaddr;
ZeroMemory(&myaddr, sizeof(myaddr));
result = InetPton(AF_INET, clientIP.c_str(), &(myaddr.sin_addr));
if(result)
ipv4addr = ntohl(myaddr.sin_addr.s_addr);
#else
result = inet_pton(AF_INET, clientIP.c_str(), &ipv4addr);
if(result)
ipv4addr = ntohl(ipv4addr);
#endif
std::string internalAddress = std::string(net.GetInternalWorldAddress());
std::string address = std::string(net.GetWorldAddress());
int16 worldport = net.GetWorldPort();
if(peerAddress.size() > 0 && peerPort > 0) {
internalAddress = peerInternalAddress;
address = peerAddress;
worldport = peerPort;
}
if (((result > 0 && IsPrivateAddress(ipv4addr)) || (strcmp(address.c_str(), clientIP.c_str()) == 0)) && (internalAddress.size() > 0))
strcpy(utwrs->ip_address, internalAddress.c_str());
else
strcpy(utwrs->ip_address, address.c_str());
LogWrite(CCLIENT__INFO, 0, "World", "New client login attempt from %s, providing %s:%u as the world server address.",clientIP.c_str(), utwrs->ip_address, worldport );
utwrs->port = worldport;
utwrs->worldid = world_id;
SendPacket(outpack);
delete outpack;
}

View File

@ -0,0 +1,88 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LOGINSERVER_H
#define LOGINSERVER_H
#include "../common/servertalk.h"
#include "../common/linked_list.h"
#include "../common/timer.h"
#include "../common/queue.h"
#include "../common/TCPConnection.h"
#include <deque>
#include "MutexMap.h"
#ifdef WIN32
void AutoInitLoginServer(void *tmp);
#else
void *AutoInitLoginServer(void *tmp);
#endif
bool InitLoginServer();
class LoginServer{
public:
LoginServer(const char* iAddress = 0, int16 iPort = 5999);
~LoginServer();
bool Process();
bool Connect(const char* iAddress = 0, int16 iPort = 0);
bool ConnectToUpdateServer(const char* iAddress = 0, int16 iPort = 0);
void SendInfo();
void SendStatus();
void GetLatestTables();
void SendPacket(ServerPacket* pack) { tcpc->SendPacket(pack); }
int8 GetState() { return tcpc->GetState(); }
bool Connected() { return tcpc->Connected(); }
void SendFilterNameResponse ( int8 resp , int32 acct_id , int32 char_id );
void SendDeleteCharacter ( CharacterTimeStamp_Struct* cts );
int32 DetermineCharacterLoginRequest ( UsertoWorldRequest_Struct* utwr, ZoneChangeDetails* details, std::string name);
void SendCharApprovedLogin(int8 response, std::string peerAddress, std::string peerInternalAddress, std::string clientIP, int16 peerPort, int32 account_id, int32 char_id, int32 key, int32 world_id, int32 from_id);
void InitLoginServerVariables();
sint16 minLockedStatus;
sint16 maxPlayers;
sint16 minGameFullStatus;
void SendImmediateEquipmentUpdatesForChar(int32 char_id);
bool CanReconnect() { return pTryReconnect; }
private:
bool try_auto_update;
bool pTryReconnect;
TCPConnection* tcpc;
int32 LoginServerIP;
int32 UpdateServerIP;
int16 LoginServerPort;
uchar* data_waiting;
MutexMap<int32, LoginZoneUpdate>* zone_updates;
MutexMap<int32, LoginEquipmentUpdate>* loginEquip_updates;
int32 last_checked_time;
Timer* statusupdate_timer;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,683 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LUA_FUNCTIONS_H
#define LUA_FUNCTIONS_H
#include <lua.hpp>
#include <vector>
#include <string>
#include <map>
using namespace std;
vector<string> ParseString(string strVal, char delim=',');
vector<unsigned int> ParseStringToInt32(string strVal, char delim=',');
map<string, signed int> ParseStringMap(string strVal, char delim=',');
map<unsigned int, unsigned short> ParseIntMap(string strVal, char delim = ',');
map<unsigned int, signed int> ParseSInt32Map(string strVal, char delim = ',');
//Sets
int EQ2Emu_lua_SetCurrentHP(lua_State* state);
int EQ2Emu_lua_SetMaxHP(lua_State* state);
int EQ2Emu_lua_SetMaxHPBase(lua_State* state);
int EQ2Emu_lua_SetCurrentPower(lua_State* state);
int EQ2Emu_lua_SetMaxPower(lua_State* state);
int EQ2Emu_lua_SetMaxPowerBase(lua_State* state);
int EQ2Emu_lua_ModifyMaxHP(lua_State* state);
int EQ2Emu_lua_ModifyMaxPower(lua_State* state);
int EQ2Emu_lua_SetHeading(lua_State* state);
int EQ2Emu_lua_SetModelType(lua_State* state);
int EQ2Emu_lua_SetAdventureClass(lua_State* state);
int EQ2Emu_lua_SetTradeskillClass(lua_State* state);
int EQ2Emu_lua_SetMount(lua_State* state);
int EQ2Emu_lua_SetMountColor(lua_State* state);
int EQ2Emu_lua_SetSpeed(lua_State* state);
int EQ2Emu_lua_SetPosition(lua_State* state);
int EQ2Emu_lua_AddSpellBonus(lua_State* state);
int EQ2Emu_lua_RemoveSpellBonus(lua_State* state);
int EQ2Emu_lua_AddSkillBonus(lua_State* state);
int EQ2Emu_lua_RemoveSkillBonus(lua_State* state);
int EQ2Emu_lua_AddControlEffect(lua_State* state);
int EQ2Emu_lua_RemoveControlEffect(lua_State* state);
int EQ2Emu_lua_HasControlEffect(lua_State* state);
int EQ2Emu_lua_GetBaseAggroRadius(lua_State* state);
int EQ2Emu_lua_GetAggroRadius(lua_State* state);
int EQ2Emu_lua_SetAggroRadius(lua_State* state);
int EQ2Emu_lua_SetDeity(lua_State* state);
int EQ2Emu_lua_GetDeity(lua_State* state);
int EQ2Emu_lua_SetInt(lua_State* state);
int EQ2Emu_lua_SetWis(lua_State* state);
int EQ2Emu_lua_SetSta(lua_State* state);
int EQ2Emu_lua_SetStr(lua_State* state);
int EQ2Emu_lua_SetAgi(lua_State* state);
int EQ2Emu_lua_SetIntBase(lua_State* state);
int EQ2Emu_lua_SetWisBase(lua_State* state);
int EQ2Emu_lua_SetStaBase(lua_State* state);
int EQ2Emu_lua_SetStrBase(lua_State* state);
int EQ2Emu_lua_SetAgiBase(lua_State* state);
int EQ2Emu_lua_SetLootCoin(lua_State* state);
int EQ2Emu_lua_HasCoin(lua_State* state);
int EQ2Emu_lua_SetQuestYellow(lua_State* state);
//Gets
int EQ2Emu_lua_GetLevel(lua_State* state);
int EQ2Emu_lua_GetDifficulty(lua_State* state);
int EQ2Emu_lua_GetCurrentHP(lua_State* state);
int EQ2Emu_lua_GetMaxHP(lua_State* state);
int EQ2Emu_lua_GetMaxHPBase(lua_State* state);
int EQ2Emu_lua_GetCurrentPower(lua_State* state);
int EQ2Emu_lua_GetName(lua_State* state);
int EQ2Emu_lua_GetMaxPower(lua_State* state);
int EQ2Emu_lua_GetMaxPowerBase(lua_State* state);
int EQ2Emu_lua_GetDistance(lua_State* state);
int EQ2Emu_lua_GetX(lua_State* state);
int EQ2Emu_lua_GetY(lua_State* state);
int EQ2Emu_lua_GetZ(lua_State* state);
int EQ2Emu_lua_GetHeading(lua_State* state);
int EQ2Emu_lua_GetModelType(lua_State* state);
int EQ2Emu_lua_GetRace(lua_State* state);
int EQ2Emu_lua_GetRaceName(lua_State* state);
int EQ2Emu_lua_GetMount(lua_State* state);
int EQ2Emu_lua_GetClass(lua_State* state);
int EQ2Emu_lua_GetClassName(lua_State* state);
int EQ2Emu_lua_GetArchetypeName(lua_State* state);
int EQ2Emu_lua_GetSpeed(lua_State* state);
int EQ2Emu_lua_HasMoved(lua_State* state);
int EQ2Emu_lua_GetInt(lua_State* state);
int EQ2Emu_lua_GetWis(lua_State* state);
int EQ2Emu_lua_GetSta(lua_State* state);
int EQ2Emu_lua_GetStr(lua_State* state);
int EQ2Emu_lua_GetAgi(lua_State* state);
int EQ2Emu_lua_GetIntBase(lua_State* state);
int EQ2Emu_lua_GetWisBase(lua_State* state);
int EQ2Emu_lua_GetStaBase(lua_State* state);
int EQ2Emu_lua_GetStrBase(lua_State* state);
int EQ2Emu_lua_GetAgiBase(lua_State* state);
int EQ2Emu_lua_GetLootCoin(lua_State* state);
int EQ2Emu_lua_GetSpawn(lua_State* state);
int EQ2Emu_lua_GetSpawnFromList(lua_State* state);
int EQ2Emu_lua_GetSpawnListSize(lua_State* state);
int EQ2Emu_lua_CreateSpawnList(lua_State* state);
int EQ2Emu_lua_AddSpawnToSpawnList(lua_State* state);
int EQ2Emu_lua_RemoveSpawnFromSpawnList(lua_State* state);
int EQ2Emu_lua_GetSpawnListBySpawnID(lua_State* state);
int EQ2Emu_lua_GetSpawnListByRailID(lua_State* state);
int EQ2Emu_lua_GetPassengerSpawnList(lua_State* state);
int EQ2Emu_lua_GetVariableValue(lua_State* state);
int EQ2Emu_lua_GetCoinMessage(lua_State* state);
int EQ2Emu_lua_GetSpawnByGroupID(lua_State* state);
int EQ2Emu_lua_GetSpawnByLocationID(lua_State* state);
int EQ2Emu_lua_GetID(lua_State* state);
int EQ2Emu_lua_GetSpawnID(lua_State* state);
int EQ2Emu_lua_GetSpawnGroupID(lua_State* state);
int EQ2Emu_lua_SetSpawnGroupID(lua_State* state);
int EQ2Emu_lua_AddSpawnToGroup(lua_State* state);
int EQ2Emu_lua_GetSpawnLocationID(lua_State* state);
int EQ2Emu_lua_GetSpawnLocationPlacementID(lua_State* state);
int EQ2Emu_lua_GetFactionAmount(lua_State* state);
int EQ2Emu_lua_SetFactionID(lua_State* state);
int EQ2Emu_lua_GetFactionID(lua_State* state);
int EQ2Emu_lua_ChangeFaction(lua_State* state);
int EQ2Emu_lua_GetGender(lua_State* state);
int EQ2Emu_lua_GetTarget(lua_State* state);
int EQ2Emu_lua_HasFreeSlot(lua_State* state);
int EQ2Emu_lua_HasItemEquipped(lua_State* state);
int EQ2Emu_lua_GetEquippedItemBySlot(lua_State* state);
int EQ2Emu_lua_GetEquippedItemByID(lua_State* state);
int EQ2Emu_lua_SetEquippedItemByID(lua_State* state);
int EQ2Emu_lua_SetEquippedItem(lua_State* state);
int EQ2Emu_lua_UnequipSlot(lua_State* state);
int EQ2Emu_lua_SetEquipment(lua_State* state);
int EQ2Emu_lua_GetItemByID(lua_State* state);
int EQ2Emu_lua_GetItemType(lua_State* state);
int EQ2Emu_lua_GetItemEffectType(lua_State* state);
int EQ2Emu_lua_GetSpellName(lua_State* state);
//Misc
int EQ2Emu_lua_SetAttackable(lua_State* state);
int EQ2Emu_lua_SendStateCommand(lua_State* state);
int EQ2Emu_lua_SpawnSet(lua_State* state);
int EQ2Emu_lua_KillSpawn(lua_State* state);
int EQ2Emu_lua_KillSpawnByDistance(lua_State* state);
int EQ2Emu_lua_SpawnSetByDistance(lua_State* state);
int EQ2Emu_lua_SetRequiredQuest(lua_State* state);
int EQ2Emu_lua_SetRequiredHistory(lua_State* state);
int EQ2Emu_lua_Despawn(lua_State* state);
int EQ2Emu_lua_ChangeHandIcon(lua_State* state);
int EQ2Emu_lua_SetVisualFlag(lua_State* state);
int EQ2Emu_lua_SetInfoFlag(lua_State* state);
int EQ2Emu_lua_AddHate(lua_State* state);
int EQ2Emu_lua_GetZone(lua_State* state);
int EQ2Emu_lua_GetZoneName(lua_State* state);
int EQ2Emu_lua_GetZoneID(lua_State* state);
int EQ2Emu_lua_Zone(lua_State* state);
int EQ2Emu_lua_ModifyPower(lua_State* state);
int EQ2Emu_lua_ModifyHP(lua_State* state);
int EQ2Emu_lua_ModifyTotalPower(lua_State* state);
int EQ2Emu_lua_ModifyTotalHP(lua_State* state);
int EQ2Emu_lua_SpellHeal(lua_State* state);
int EQ2Emu_lua_SpellHealPct(lua_State* state);
int EQ2Emu_lua_AddItem(lua_State* state);
int EQ2Emu_lua_SummonItem(lua_State* state);
int EQ2Emu_lua_RemoveItem(lua_State* state);
int EQ2Emu_lua_HasItem(lua_State* state);
int EQ2Emu_lua_Spawn(lua_State* state);
int EQ2Emu_lua_AddSpawnAccess(lua_State* state);
int EQ2Emu_lua_CastSpell(lua_State* state);
int EQ2Emu_lua_SpellDamage(lua_State* state);
int EQ2Emu_lua_SpellDamageExt(lua_State* state);
int EQ2Emu_lua_FaceTarget(lua_State* state);
int EQ2Emu_lua_MoveToLocation(lua_State* state);
int EQ2Emu_lua_ClearRunningLocations(lua_State* state);
int EQ2Emu_lua_Say(lua_State* state);
int EQ2Emu_lua_Shout(lua_State* state);
int EQ2Emu_lua_SayOOC(lua_State* state);
int EQ2Emu_lua_Emote(lua_State* state);
int EQ2Emu_lua_IsPlayer(lua_State* state);
int EQ2Emu_lua_GetCharacterID(lua_State* state);
int EQ2Emu_lua_MovementLoopAdd(lua_State* state);
int EQ2Emu_lua_GetCurrentZoneSafeLocation(lua_State* state);
int EQ2Emu_lua_PlayFlavor(lua_State* state);
int EQ2Emu_lua_PlayFlavorID(lua_State* state);
int EQ2Emu_lua_PlaySound(lua_State* state);
int EQ2Emu_lua_PlayVoice(lua_State* state);
int EQ2Emu_lua_PlayAnimation(lua_State* state);
int EQ2Emu_lua_PlayAnimationString(lua_State* state);
int EQ2Emu_lua_AddLootItem(lua_State* state);
int EQ2Emu_lua_HasLootItem(lua_State* state);
int EQ2Emu_lua_RemoveLootItem(lua_State* state);
int EQ2Emu_lua_AddLootCoin(lua_State* state);
int EQ2Emu_lua_GiveLoot(lua_State* state);
int EQ2Emu_lua_HasPendingLoot(lua_State* state);
int EQ2Emu_lua_HasPendingLootItem(lua_State* state);
int EQ2Emu_lua_CreateConversation(lua_State* state);
int EQ2Emu_lua_AddConversationOption(lua_State* state);
int EQ2Emu_lua_StartConversation(lua_State* state);
int EQ2Emu_lua_StartDialogConversation(lua_State* state);
//int EQ2Emu_lua_StartItemConversation(lua_State* state);
int EQ2Emu_lua_CloseConversation(lua_State* state);
int EQ2Emu_lua_CloseItemConversation(lua_State* state);
int EQ2Emu_lua_SetPlayerProximityFunction(lua_State* state);
int EQ2Emu_lua_SetLocationProximityFunction(lua_State* state);
int EQ2Emu_lua_IsBindAllowed(lua_State* state);
int EQ2Emu_lua_IsGateAllowed(lua_State* state);
int EQ2Emu_lua_Bind(lua_State* state);
int EQ2Emu_lua_Gate(lua_State* state);
int EQ2Emu_lua_IsAlive(lua_State* state);
int EQ2Emu_lua_IsSpawnGroupAlive(lua_State* state);
int EQ2Emu_lua_IsInCombat(lua_State* state);
int EQ2Emu_lua_SendMessage(lua_State* state);
int EQ2Emu_lua_SendPopUpMessage(lua_State* state);
int EQ2Emu_lua_SetServerControlFlag(lua_State* state);
int EQ2Emu_lua_ToggleTracking(lua_State* state);
int EQ2Emu_lua_AddPrimaryEntityCommand(lua_State* state);
int EQ2Emu_lua_AddSpellBookEntry(lua_State* state);
int EQ2Emu_lua_DeleteSpellBook(lua_State* state);
int EQ2Emu_lua_RemoveSpellBookEntry(lua_State* state);
int EQ2Emu_lua_SendNewAdventureSpells(lua_State* state);
int EQ2Emu_lua_SendNewTradeskillSpells(lua_State* state);
int EQ2Emu_lua_HasSpell(lua_State* state);
int EQ2Emu_lua_Attack(lua_State* state);
int EQ2Emu_lua_ApplySpellVisual(lua_State* state);
int EQ2Emu_lua_Interrupt(lua_State* state);
int EQ2Emu_lua_Stealth(lua_State* state);
int EQ2Emu_lua_IsStealthed(lua_State* state);
int EQ2Emu_lua_IsInvis(lua_State* state);
int EQ2Emu_lua_AddSpawnIDAccess(lua_State* state);
int EQ2Emu_lua_RemoveSpawnIDAccess(lua_State* state);
int EQ2Emu_lua_HasRecipeBook(lua_State* state);
int EQ2Emu_lua_SpawnMove(lua_State* state);
int EQ2Emu_lua_AddTransportSpawn(lua_State* state);
int EQ2Emu_lua_IsTransportSpawn(lua_State* state);
int EQ2Emu_lua_PerformCameraShake(lua_State* state);
//Quest Stuff
int EQ2Emu_lua_SetStepComplete(lua_State* state);
int EQ2Emu_lua_AddStepProgress(lua_State* state);
int EQ2Emu_lua_GetTaskGroupStep(lua_State* state);
int EQ2Emu_lua_QuestStepIsComplete(lua_State* state);
int EQ2Emu_lua_GetQuestStep(lua_State* state);
int EQ2Emu_lua_RegisterQuest(lua_State* state);
int EQ2Emu_lua_OfferQuest(lua_State* state);
int EQ2Emu_lua_SetQuestPrereqLevel(lua_State* state);
int EQ2Emu_lua_AddQuestPrereqQuest(lua_State* state);
int EQ2Emu_lua_AddQuestPrereqItem(lua_State* state);
int EQ2Emu_lua_AddQuestPrereqFaction(lua_State* state);
int EQ2Emu_lua_AddQuestPrereqClass(lua_State* state);
int EQ2Emu_lua_AddQuestPrereqRace(lua_State* state);
int EQ2Emu_lua_AddQuestPrereqModelType(lua_State* state);
int EQ2Emu_lua_AddQuestPrereqTradeskillLevel(lua_State* state);
int EQ2Emu_lua_AddQuestPrereqTradeskillClass(lua_State* state);
int EQ2Emu_lua_HasQuestRewardItem(lua_State* state);
int EQ2Emu_lua_AddQuestRewardItem(lua_State* state);
int EQ2Emu_lua_AddQuestSelectableRewardItem(lua_State* state);
int EQ2Emu_lua_AddQuestRewardCoin(lua_State* state);
int EQ2Emu_lua_AddQuestRewardFaction(lua_State* state);
int EQ2Emu_lua_SetQuestRewardStatus(lua_State* state);
int EQ2Emu_lua_SetStatusTmpReward(lua_State* state);
int EQ2Emu_lua_SetCoinTmpReward(lua_State* state);
int EQ2Emu_lua_SetQuestRewardComment(lua_State* state);
int EQ2Emu_lua_SetQuestRewardExp(lua_State* state);
int EQ2Emu_lua_AddQuestStep(lua_State* state);
int EQ2Emu_lua_AddQuestStepKillLogic(lua_State* state);
int EQ2Emu_lua_AddQuestStepKill(lua_State* state);
int EQ2Emu_lua_AddQuestStepKillByRace(lua_State* state);
int EQ2Emu_lua_AddQuestStepChat(lua_State* state);
int EQ2Emu_lua_AddQuestStepObtainItem(lua_State* state);
int EQ2Emu_lua_AddQuestStepZoneLoc(lua_State* state);
int EQ2Emu_lua_AddQuestStepLocation(lua_State* state);
int EQ2Emu_lua_AddQuestStepLoc(lua_State* state);
int EQ2Emu_lua_AddQuestStepSpell(lua_State* state);
int EQ2Emu_lua_AddQuestStepCraft(lua_State* state);
int EQ2Emu_lua_AddQuestStepHarvest(lua_State* state);
int EQ2Emu_lua_AddQuestStepCompleteAction(lua_State* state);
int EQ2Emu_lua_AddQuestStepProgressAction(lua_State* state);
int EQ2Emu_lua_SetQuestCompleteAction(lua_State* state);
int EQ2Emu_lua_GiveQuestReward(lua_State* state);
int EQ2Emu_lua_UpdateQuestTaskGroupDescription(lua_State* state);
int EQ2Emu_lua_UpdateQuestStepDescription(lua_State* state);
int EQ2Emu_lua_UpdateQuestDescription(lua_State* state);
int EQ2Emu_lua_UpdateQuestZone(lua_State* state);
int EQ2Emu_lua_SetCompletedDescription(lua_State* state);
int EQ2Emu_lua_ProvidesQuest(lua_State* state);
int EQ2Emu_lua_HasQuest(lua_State* state);
int EQ2Emu_lua_HasPendingQuest(lua_State* state);
int EQ2Emu_lua_HasCompletedQuest(lua_State* state);
int EQ2Emu_lua_QuestIsComplete(lua_State* state);
int EQ2Emu_lua_QuestReturnNPC(lua_State* state);
int EQ2Emu_lua_GetQuest(lua_State* state);
int EQ2Emu_lua_AddTimer(lua_State* state);
int EQ2Emu_lua_StopTimer(lua_State* state);
int EQ2Emu_lua_Harvest(lua_State* state);
int EQ2Emu_lua_SetCompleteFlag(lua_State* state);
int EQ2Emu_lua_CanReceiveQuest(lua_State* state);
int EQ2Emu_lua_HasCollectionsToHandIn(lua_State *state);
int EQ2Emu_lua_HandInCollections(lua_State *state);
int EQ2Emu_lua_UseWidget(lua_State* state);
int EQ2Emu_lua_SummonPet(lua_State* state);
int EQ2Emu_lua_Charm(lua_State* state);
int EQ2Emu_lua_SetSpellList(lua_State* state);
int EQ2Emu_lua_GetPet(lua_State* state);
int EQ2Emu_lua_GetGroup(lua_State* state);
int EQ2Emu_lua_CreateOptionWindow(lua_State* state);
int EQ2Emu_lua_AddOptionWindowOption(lua_State* state);
int EQ2Emu_lua_SendOptionWindow(lua_State* state);
int EQ2Emu_lua_GetTradeskillClass(lua_State* state);
int EQ2Emu_lua_GetTradeskillLevel(lua_State* state);
int EQ2Emu_lua_GetTradeskillClassName(lua_State* state);
int EQ2Emu_lua_SetTradeskillLevel(lua_State* state);
int EQ2Emu_lua_SummonDeityPet(lua_State* state);
int EQ2Emu_lua_SummonCosmeticPet(lua_State* state);
int EQ2Emu_lua_DismissPet(lua_State* state);
int EQ2Emu_lua_GetCharmedPet(lua_State* state);
int EQ2Emu_lua_GetDeityPet(lua_State* state);
int EQ2Emu_lua_GetCosmeticPet(lua_State* state);
int EQ2Emu_lua_SetQuestFeatherColor(lua_State* state);
int EQ2Emu_lua_RemoveSpawnAccess(lua_State* state);
int EQ2Emu_lua_SpawnByLocationID(lua_State* state);
int EQ2Emu_lua_SpawnGroupByID(lua_State* state);
int EQ2Emu_lua_CastEntityCommand(lua_State* state);
int EQ2Emu_lua_SetLuaBrain(lua_State* state);
int EQ2Emu_lua_SetBrainTick(lua_State* state);
int EQ2Emu_lua_SetFollowTarget(lua_State* state);
int EQ2Emu_lua_GetFollowTarget(lua_State* state);
int EQ2Emu_lua_ToggleFollow(lua_State* state);
int EQ2Emu_lua_IsFollowing(lua_State* state);
int EQ2Emu_lua_SetTempVariable(lua_State* state);
int EQ2Emu_lua_GetTempVariable(lua_State* state);
int EQ2Emu_lua_GiveQuestItem(lua_State*state);
int EQ2Emu_lua_SetQuestRepeatable(lua_State* state);
int EQ2Emu_lua_AddWaypoint(lua_State* state);
int EQ2Emu_lua_RemoveWaypoint(lua_State* state);
int EQ2Emu_lua_SendWaypoints(lua_State* state);
int EQ2Emu_lua_AddWard(lua_State* state);
int EQ2Emu_lua_AddToWard(lua_State* state);
int EQ2Emu_lua_RemoveWard(lua_State* state);
int EQ2Emu_lua_GetWardAmountLeft(lua_State* state);
int EQ2Emu_lua_GetWardValue(lua_State* state);
//Combat AI related
int EQ2Emu_lua_SetTarget(lua_State* state);
int EQ2Emu_lua_IsPet(lua_State* state);
int EQ2Emu_lua_GetOwner(lua_State* state);
int EQ2Emu_lua_SetInCombat(lua_State* state);
int EQ2Emu_lua_CompareSpawns(lua_State* state);
int EQ2Emu_lua_ClearRunback(lua_State* state);
int EQ2Emu_lua_Runback(lua_State* state);
int EQ2Emu_lua_GetRunbackDistance(lua_State* state);
int EQ2Emu_lua_IsCasting(lua_State* state);
int EQ2Emu_lua_IsMezzed(lua_State* state);
int EQ2Emu_lua_IsStunned(lua_State* state);
int EQ2Emu_lua_IsMezzedOrStunned(lua_State* state);
int EQ2Emu_lua_ClearEncounter(lua_State* state);
int EQ2Emu_lua_ClearHate(lua_State* state);
int EQ2Emu_lua_GetMostHated(lua_State* state);
int EQ2Emu_lua_GetEncounterSize(lua_State* state);
int EQ2Emu_lua_HasRecovered(lua_State* state);
int EQ2Emu_lua_ProcessMelee(lua_State* state);
int EQ2Emu_lua_ProcessSpell(lua_State* state);
int EQ2Emu_lua_GetEncounter(lua_State* state);
int EQ2Emu_lua_GetHateList(lua_State* state);
int EQ2Emu_lua_HasGroup(lua_State* state);
int EQ2Emu_lua_HasSpellEffect(lua_State* state);
int EQ2Emu_lua_SetSuccessTimer(lua_State* state);
int EQ2Emu_lua_SetFailureTimer(lua_State* state);
int EQ2Emu_lua_IsGroundSpawn(lua_State* state);
int EQ2Emu_lua_CanHarvest(lua_State* state);
int EQ2Emu_lua_SummonDumbFirePet(lua_State* state);
int EQ2Emu_lua_GetSkillValue(lua_State* state);
int EQ2Emu_lua_GetSkillMaxValue(lua_State* state);
int EQ2Emu_lua_GetSkillName(lua_State* state);
int EQ2Emu_lua_SetSkillMaxValue(lua_State* state);
int EQ2Emu_lua_SetSkillValue(lua_State* state);
int EQ2Emu_lua_GetSkill(lua_State* state);
int EQ2Emu_lua_GetSkillIDByName(lua_State* state);
int EQ2Emu_lua_HasSkill(lua_State* state);
int EQ2Emu_lua_AddSkill(lua_State* state);
int EQ2Emu_lua_RemoveSkill(lua_State* state);
int EQ2Emu_lua_IncreaseSkillCapsByType(lua_State* state);
int EQ2Emu_lua_AddProc(lua_State* state);
int EQ2Emu_lua_AddProcExt(lua_State* state);
int EQ2Emu_lua_RemoveProc(lua_State* state);
int EQ2Emu_lua_Knockback(lua_State* state);
int EQ2Emu_lua_IsEpic(lua_State* state);
int EQ2Emu_lua_IsHeroic(lua_State* state);
int EQ2Emu_lua_ProcDamage(lua_State* state);
int EQ2Emu_lua_LastSpellAttackHit(lua_State* state);
int EQ2Emu_lua_IsBehind(lua_State* state);
int EQ2Emu_lua_IsFlanking(lua_State* state);
int EQ2Emu_lua_InFront(lua_State* state);
int EQ2Emu_lua_AddSpellTimer(lua_State* state);
int EQ2Emu_lua_SetItemCount(lua_State* state);
int EQ2Emu_lua_GetItemCount(lua_State* state);
int EQ2Emu_lua_Resurrect(lua_State* state);
int EQ2Emu_lua_BreatheUnderwater(lua_State* state);
int EQ2Emu_lua_BlurVision(lua_State* state);
int EQ2Emu_lua_SetVision(lua_State* state);
int EQ2Emu_lua_GetItemSkillReq(lua_State* state);
int EQ2Emu_lua_SetSpeedMultiplier(lua_State* state);
int EQ2Emu_lua_SetIllusion(lua_State* state);
int EQ2Emu_lua_ResetIllusion(lua_State* state);
int EQ2Emu_lua_AddThreatTransfer(lua_State* state);
int EQ2Emu_lua_RemoveThreatTransfer(lua_State* state);
int EQ2Emu_lua_CureByType(lua_State* state);
int EQ2Emu_lua_CureByControlEffect(lua_State* state);
int EQ2Emu_lua_AddSpawnSpellBonus(lua_State* state);
int EQ2Emu_lua_RemoveSpawnSpellBonus(lua_State* state);
int EQ2Emu_lua_CancelSpell(lua_State* state);
int EQ2Emu_lua_RemoveStealth(lua_State* state);
int EQ2Emu_lua_RemoveInvis(lua_State* state);
int EQ2Emu_lua_StartHeroicOpportunity(lua_State* state);
int EQ2Emu_lua_CopySpawnAppearance(lua_State* state);
int EQ2Emu_lua_RemoveTriggerFromSpell(lua_State* state);
int EQ2Emu_lua_GetSpellTriggerCount(lua_State* state);
int EQ2Emu_lua_SetSpellTriggerCount(lua_State* state);
int EQ2Emu_lua_HasSpellImmunity(lua_State* state);
int EQ2Emu_lua_AddImmunitySpell(lua_State* state);
int EQ2Emu_lua_RemoveImmunitySpell(lua_State* state);
int EQ2Emu_lua_SetSpellSnareValue(lua_State* state);
int EQ2Emu_lua_CheckRaceType(lua_State* state);
int EQ2Emu_lua_GetRaceType(lua_State* state);
int EQ2Emu_lua_GetRaceBaseType(lua_State* state);
int EQ2Emu_lua_GetQuestFlags(lua_State* state);
int EQ2Emu_lua_SetQuestFlags(lua_State* state);
int EQ2Emu_lua_SetQuestTimer(lua_State* state);
int EQ2Emu_lua_RemoveQuestStep(lua_State* state);
int EQ2Emu_lua_ResetQuestStep(lua_State* state);
int EQ2Emu_lua_SetQuestTimerComplete(lua_State* state);
int EQ2Emu_lua_AddQuestStepFailureAction(lua_State* state);
int EQ2Emu_lua_SetStepFailed(lua_State* state);
int EQ2Emu_lua_GetQuestCompleteCount(lua_State* state);
int EQ2Emu_lua_SetServerVariable(lua_State* state);
int EQ2Emu_lua_GetServerVariable(lua_State* state);
int EQ2Emu_lua_HasLanguage(lua_State* state);
int EQ2Emu_lua_AddLanguage(lua_State* state);
int EQ2Emu_lua_IsNight(lua_State* state);
int EQ2Emu_lua_AddMultiFloorLift(lua_State* state);
int EQ2Emu_lua_StartAutoMount(lua_State* state);
int EQ2Emu_lua_EndAutoMount(lua_State* state);
int EQ2Emu_lua_IsOnAutoMount(lua_State* state);
int EQ2Emu_lua_SetPlayerHistory(lua_State* state);
int EQ2Emu_lua_GetPlayerHistory(lua_State* state);
int EQ2Emu_lua_SetGridID(lua_State* state);
int EQ2Emu_lua_GetQuestStepProgress(lua_State* state);
int EQ2Emu_lua_SetPlayerLevel(lua_State* state);
int EQ2Emu_lua_AddCoin(lua_State* state);
int EQ2Emu_lua_RemoveCoin(lua_State* state);
int EQ2Emu_lua_GetPlayersInZone(lua_State* state);
int EQ2Emu_lua_SetSpawnAnimation(lua_State* state);
int EQ2Emu_lua_GetClientVersion(lua_State* state);
int EQ2Emu_lua_GetItemID(lua_State* state);
int EQ2Emu_lua_IsEntity(lua_State* state);
int EQ2Emu_lua_GetOrigX(lua_State* state);
int EQ2Emu_lua_GetOrigY(lua_State* state);
int EQ2Emu_lua_GetOrigZ(lua_State* state);
int EQ2Emu_lua_GetPCTOfHP(lua_State* state);
int EQ2Emu_lua_GetPCTOfPower(lua_State* state);
int EQ2Emu_lua_GetBoundZoneID(lua_State* state);
int EQ2Emu_lua_Evac(lua_State* state);
int EQ2Emu_lua_GetSpellTier(lua_State* state);
int EQ2Emu_lua_GetSpellID(lua_State* state);
int EQ2Emu_lua_StartTransmute(lua_State* state);
int EQ2Emu_lua_CompleteTransmute(lua_State* state);
int EQ2Emu_lua_ProcHate(lua_State* state);
int EQ2Emu_lua_GiveExp(lua_State* state);
int EQ2Emu_lua_DisplayText(lua_State* state);
int EQ2Emu_lua_ShowLootWindow(lua_State* state);
int EQ2Emu_lua_GetRandomSpawnByID(lua_State* state);
int EQ2Emu_lua_AddPrimaryEntityCommandAllSpawns(lua_State* state);
int EQ2Emu_lua_InstructionWindow(lua_State* state);
int EQ2Emu_lua_InstructionWindowClose(lua_State* state);
int EQ2Emu_lua_InstructionWindowGoal(lua_State* state);
int EQ2Emu_lua_ShowWindow(lua_State* state);
int EQ2Emu_lua_FlashWindow(lua_State* state);
int EQ2Emu_lua_EnableGameEvent(lua_State* state);
int EQ2Emu_lua_GetTutorialStep(lua_State* state);
int EQ2Emu_lua_SetTutorialStep(lua_State* state);
int EQ2Emu_lua_CheckLOS(lua_State* state);
int EQ2Emu_lua_CheckLOSByCoordinates(lua_State* state);
int EQ2Emu_lua_SetZoneExpansionFlag(lua_State* state);
int EQ2Emu_lua_GetZoneExpansionFlag(lua_State* state);
int EQ2Emu_lua_SetZoneHolidayFlag(lua_State* state);
int EQ2Emu_lua_GetZoneHolidayFlag(lua_State* state);
int EQ2Emu_lua_SetCanBind(lua_State* state);
int EQ2Emu_lua_GetCanBind(lua_State* state);
int EQ2Emu_lua_GetCanGate(lua_State* state);
int EQ2Emu_lua_SetCanGate(lua_State* state);
int EQ2Emu_lua_GetCanEvac(lua_State* state);
int EQ2Emu_lua_SetCanEvac(lua_State* state);
int EQ2Emu_lua_AddSpawnProximity(lua_State* state);
int EQ2Emu_lua_CanSeeInvis(lua_State* state);
int EQ2Emu_lua_SetSeeInvis(lua_State* state);
int EQ2Emu_lua_SetSeeHide(lua_State* state);
int EQ2Emu_lua_SetAccessToEntityCommand(lua_State* state);
int EQ2Emu_lua_SetAccessToEntityCommandByCharID(lua_State* state);
int EQ2Emu_lua_RemovePrimaryEntityCommand(lua_State* state);
int EQ2Emu_lua_SendUpdateDefaultCommand(lua_State* state);
int EQ2Emu_lua_SendTransporters(lua_State* state);
int EQ2Emu_lua_SetTemporaryTransportID(lua_State* state);
int EQ2Emu_lua_GetTemporaryTransportID(lua_State* state);
int EQ2Emu_lua_GetAlignment(lua_State* state);
int EQ2Emu_lua_SetAlignment(lua_State* state);
int EQ2Emu_lua_GetSpell(lua_State* state);
int EQ2Emu_lua_GetSpellData(lua_State* state);
int EQ2Emu_lua_SetSpellData(lua_State* state);
int EQ2Emu_lua_CastCustomSpell(lua_State* state);
int EQ2Emu_lua_SetSpellDataIndex(lua_State* state);
int EQ2Emu_lua_GetSpellDataIndex(lua_State* state);
int EQ2Emu_lua_SetSpellDisplayEffect(lua_State* state);
int EQ2Emu_lua_GetSpellDisplayEffect(lua_State* state);
int EQ2Emu_lua_InWater(lua_State* state);
int EQ2Emu_lua_InLava(lua_State* state);
int EQ2Emu_lua_DamageSpawn(lua_State* state);
int EQ2Emu_lua_IsInvulnerable(lua_State* state);
int EQ2Emu_lua_SetInvulnerable(lua_State* state);
int EQ2Emu_lua_GetRuleFlagBool(lua_State* state);
int EQ2Emu_lua_GetRuleFlagInt32(lua_State* state);
int EQ2Emu_lua_GetRuleFlagFloat(lua_State* state);
int EQ2Emu_lua_GetAAInfo(lua_State* state);
int EQ2Emu_lua_SetAAInfo(lua_State* state);
int EQ2Emu_lua_AddMasterTitle(lua_State* state);
int EQ2Emu_lua_AddCharacterTitle(lua_State* state);
int EQ2Emu_lua_SetCharacterTitleSuffix(lua_State* state);
int EQ2Emu_lua_SetCharacterTitlePrefix(lua_State* state);
int EQ2Emu_lua_ResetCharacterTitleSuffix(lua_State* state);
int EQ2Emu_lua_ResetCharacterTitlePrefix(lua_State* state);
int EQ2Emu_lua_GetInfoStructString(lua_State* state);
int EQ2Emu_lua_GetInfoStructUInt(lua_State* state);
int EQ2Emu_lua_GetInfoStructSInt(lua_State* state);
int EQ2Emu_lua_GetInfoStructFloat(lua_State* state);
int EQ2Emu_lua_SetInfoStructString(lua_State* state);
int EQ2Emu_lua_SetInfoStructUInt(lua_State* state);
int EQ2Emu_lua_SetInfoStructSInt(lua_State* state);
int EQ2Emu_lua_SetInfoStructFloat(lua_State* state);
int EQ2Emu_lua_SetCharSheetChanged(lua_State* state);
int EQ2Emu_lua_AddPlayerMail(lua_State* state);
int EQ2Emu_lua_AddPlayerMailByCharID(lua_State* state);
int EQ2Emu_lua_OpenDoor(lua_State* state);
int EQ2Emu_lua_CloseDoor(lua_State* state);
int EQ2Emu_lua_IsOpen(lua_State* state);
int EQ2Emu_lua_MakeRandomInt(lua_State* state);
int EQ2Emu_lua_MakeRandomFloat(lua_State* state);
int EQ2Emu_lua_AddIconValue(lua_State* state);
int EQ2Emu_lua_RemoveIconValue(lua_State* state);
int EQ2Emu_lua_GetShardID(lua_State* state);
int EQ2Emu_lua_GetShardCharID(lua_State* state);
int EQ2Emu_lua_GetShardCreatedTimestamp(lua_State* state);
int EQ2Emu_lua_DeleteDBShardID(lua_State* state);
int EQ2Emu_lua_PauseMovement(lua_State* state);
int EQ2Emu_lua_StopMovement(lua_State* state);
int EQ2Emu_lua_GetArrowColor(lua_State* state);
int EQ2Emu_lua_GetTSArrowColor(lua_State* state);
int EQ2Emu_lua_GetSpawnByRailID(lua_State* state);
int EQ2Emu_lua_SetRailID(lua_State* state);
int EQ2Emu_lua_IsZoneLoading(lua_State* state);
int EQ2Emu_lua_IsRunning(lua_State* state);
int EQ2Emu_lua_GetZoneLockoutTimer(lua_State* state);
int EQ2Emu_lua_SetWorldTime(lua_State* state);
int EQ2Emu_lua_GetWorldTimeYear(lua_State* state);
int EQ2Emu_lua_GetWorldTimeMonth(lua_State* state);
int EQ2Emu_lua_GetWorldTimeHour(lua_State* state);
int EQ2Emu_lua_GetWorldTimeMinute(lua_State* state);
int EQ2Emu_lua_SendTimeUpdate(lua_State* state);
int EQ2Emu_lua_SetLootTier(lua_State* state);
int EQ2Emu_lua_GetLootTier(lua_State* state);
int EQ2Emu_lua_SetLootDropType(lua_State* state);
int EQ2Emu_lua_GetLootDropType(lua_State* state);
int EQ2Emu_lua_DamageEquippedItems(lua_State* state);
int EQ2Emu_lua_CreateWidgetRegion(lua_State* state);
int EQ2Emu_lua_RemoveRegion(lua_State* state);
int EQ2Emu_lua_SetPlayerPOVGhost(lua_State* state);
int EQ2Emu_lua_SetCastOnAggroComplete(lua_State* state);
int EQ2Emu_lua_IsCastOnAggroComplete(lua_State* state);
int EQ2Emu_lua_AddRecipeBookToPlayer(lua_State* state);
int EQ2Emu_lua_RemoveRecipeFromPlayer(lua_State* state);
int EQ2Emu_lua_ReplaceWidgetFromClient(lua_State* state);
int EQ2Emu_lua_RemoveWidgetFromSpawnMap(lua_State* state);
int EQ2Emu_lua_RemoveWidgetFromZoneMap(lua_State* state);
int EQ2Emu_lua_SendHearCast(lua_State* state);
int EQ2Emu_lua_GetCharacterFlag(lua_State* state);
int EQ2Emu_lua_ToggleCharacterFlag(lua_State* state);
int EQ2Emu_lua_GetSpellInitialTarget(lua_State* state);
int EQ2Emu_lua_GetSpellCaster(lua_State* state);
int EQ2Emu_lua_GetCasterSpellLevel(lua_State* state);
int EQ2Emu_lua_GetSpellTargets(lua_State* state);
int EQ2Emu_lua_DespawnByLocationID(lua_State* state);
int EQ2Emu_lua_AddRespawn(lua_State* state);
int EQ2Emu_lua_CreatePersistedRespawn(lua_State* state);
int EQ2Emu_lua_CreateChoiceWindow(lua_State* state);
int EQ2Emu_lua_ClearChoice(lua_State* state);
int EQ2Emu_lua_GetChoiceSpawnID(lua_State* state);
int EQ2Emu_lua_GetZonePlayerMinLevel(lua_State* state);
int EQ2Emu_lua_GetZonePlayerMaxLevel(lua_State* state);
int EQ2Emu_lua_GetZonePlayerAvgLevel(lua_State* state);
int EQ2Emu_lua_GetZonePlayerFirstLevel(lua_State* state);
int EQ2Emu_lua_GetSpellRequiredLevel(lua_State* state);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,365 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef LUA_INTERFACE_H
#define LUA_INTERFACE_H
#include <mutex>
#include <shared_mutex>
#include "Spawn.h"
#include "Spells.h"
#include "Quests.h"
#include "zoneserver.h"
#include "client.h"
#include <lua.hpp>
using namespace std;
struct ConversationOption{
string option;
string function;
};
struct OptionWindowOption {
string optionName;
string optionDescription;
string optionCommand;
int32 optionIconSheet;
int16 optionIconID;
string optionConfirmTitle;
};
//Bitmask Values
#define EFFECT_FLAG_STUN 1
#define EFFECT_FLAG_ROOT 2
#define EFFECT_FLAG_MEZ 4
#define EFFECT_FLAG_STIFLE 8
#define EFFECT_FLAG_DAZE 16
#define EFFECT_FLAG_FEAR 32
#define EFFECT_FLAG_SPELLBONUS 64
#define EFFECT_FLAG_SKILLBONUS 128
#define EFFECT_FLAG_STEALTH 256
#define EFFECT_FLAG_INVIS 512
#define EFFECT_FLAG_SNARE 1024
#define EFFECT_FLAG_WATERWALK 2048
#define EFFECT_FLAG_WATERJUMP 4096
#define EFFECT_FLAG_FLIGHT 8192
#define EFFECT_FLAG_GLIDE 16384
#define EFFECT_FLAG_AOE_IMMUNE 32768
#define EFFECT_FLAG_STUN_IMMUNE 65536
#define EFFECT_FLAG_MEZ_IMMUNE 131072
#define EFFECT_FLAG_DAZE_IMMUNE 262144
#define EFFECT_FLAG_ROOT_IMMUNE 524288
#define EFFECT_FLAG_STIFLE_IMMUNE 1048576
#define EFFECT_FLAG_FEAR_IMMUNE 2097152
#define EFFECT_FLAG_SAFEFALL 4194304
struct LuaSpell{
Entity* caster;
int32 initial_caster_char_id;
int32 initial_target;
int32 initial_target_char_id;
vector<int32> targets;
vector<int32> removed_targets; // previously cancelled, expired, used, so on
multimap<int32, int8> char_id_targets;
Spell* spell;
lua_State* state;
string file_name;
Timer timer;
bool is_recast_timer;
int16 num_calls;
int16 num_triggers;
int8 slot_pos;
int32 damage_remaining;
bool resisted;
bool has_damaged;
bool is_damage_spell;
bool interrupted;
bool crit;
bool last_spellattack_hit;
bool cancel_after_all_triggers;
bool had_triggers;
bool had_dmg_remaining;
Mutex MSpellTargets;
Mutex MScriptMutex;
int32 effect_bitmask;
bool restored; // restored spell cross zone
std::atomic<bool> has_proc;
ZoneServer* zone;
int16 initial_caster_level;
};
class LUAUserData{
public:
LUAUserData();
virtual ~LUAUserData(){};
virtual bool IsCorrectlyInitialized();
virtual bool IsConversationOption();
virtual bool IsOptionWindow();
virtual bool IsSpawn();
virtual bool IsQuest();
virtual bool IsZone();
virtual bool IsItem();
virtual bool IsSkill();
virtual bool IsSpell();
bool correctly_initialized;
Item* item;
ZoneServer* zone;
Spawn* spawn;
vector<ConversationOption>* conversation_options;
vector<OptionWindowOption>* option_window_option;
vector<Spawn*>* spawn_list;
Quest* quest;
Skill* skill;
LuaSpell* spell;
};
class LUAConversationOptionWrapper : public LUAUserData{
public:
LUAConversationOptionWrapper();
bool IsConversationOption();
};
class LUAOptionWindowWrapper : public LUAUserData {
public:
LUAOptionWindowWrapper();
bool IsOptionWindow();
};
class LUASpawnWrapper : public LUAUserData{
public:
LUASpawnWrapper();
bool IsSpawn();
};
class LUAZoneWrapper : public LUAUserData{
public:
LUAZoneWrapper();
bool IsZone();
};
class LUAQuestWrapper : public LUAUserData{
public:
LUAQuestWrapper();
bool IsQuest();
};
class LUAItemWrapper : public LUAUserData{
public:
LUAItemWrapper();
bool IsItem();
};
class LUASkillWrapper: public LUAUserData {
public:
LUASkillWrapper();
bool IsSkill();
};
class LUASpellWrapper : public LUAUserData {
public:
LUASpellWrapper();
bool IsSpell();
};
class LuaInterface {
public:
LuaInterface();
~LuaInterface();
int GetNumberOfArgs(lua_State* state);
bool LoadItemScript(string name);
bool LoadItemScript(const char* name);
bool LoadSpawnScript(string name);
bool LoadSpawnScript(const char* name);
bool LoadZoneScript(string name);
bool LoadZoneScript(const char* name);
bool LoadRegionScript(string name);
bool LoadRegionScript(const char* name);
LuaSpell* LoadSpellScript(string name);
LuaSpell* LoadSpellScript(const char* name);
void RemoveSpell(LuaSpell* spell, bool call_remove_function = true, bool can_delete = true, string reason = "", bool removing_all_spells = false, bool return_after_call_remove = false, Spawn* overrideTarget = nullptr);
Spawn* GetSpawn(lua_State* state, int8 arg_num = 1);
Item* GetItem(lua_State* state, int8 arg_num = 1);
Quest* GetQuest(lua_State* state, int8 arg_num = 1);
ZoneServer* GetZone(lua_State* state, int8 arg_num = 1);
Skill* GetSkill(lua_State* state, int8 arg_num = 1);
LuaSpell* GetSpell(lua_State* state, int8 arg_num = 1);
vector<ConversationOption>* GetConversation(lua_State* state, int8 arg_num = 1);
vector<OptionWindowOption>* GetOptionWindow(lua_State* state, int8 arg_num = 1);
int8 GetInt8Value(lua_State* state, int8 arg_num = 1);
int16 GetInt16Value(lua_State* state, int8 arg_num = 1);
int32 GetInt32Value(lua_State* state, int8 arg_num = 1);
sint32 GetSInt32Value(lua_State* state, int8 arg_num = 1);
int64 GetInt64Value(lua_State* state, int8 arg_num = 1);
sint64 GetSInt64Value(lua_State* state, int8 arg_num = 1);
float GetFloatValue(lua_State* state, int8 arg_num = 1);
string GetStringValue(lua_State* state, int8 arg_num = 1);
bool GetBooleanValue(lua_State*state, int8 arg_num = 1);
void Process();
void SetInt32Value(lua_State* state, int32 value);
void SetSInt32Value(lua_State* state, sint32 value);
void SetInt64Value(lua_State* state, int64 value);
void SetSInt64Value(lua_State* state, sint64 value);
void SetFloatValue(lua_State* state, float value);
void SetBooleanValue(lua_State* state, bool value);
void SetStringValue(lua_State* state, const char* value);
void SetSpawnValue(lua_State* state, Spawn* spawn);
void SetSkillValue(lua_State* state, Skill* skill);
void SetItemValue(lua_State* state, Item* item);
void SetQuestValue(lua_State* state, Quest* quest);
void SetZoneValue(lua_State* state, ZoneServer* zone);
void SetSpellValue(lua_State* state, LuaSpell* spell);
void SetConversationValue(lua_State* state, vector<ConversationOption>* conversation);
void SetOptionWindowValue(lua_State* state, vector<OptionWindowOption>* optionWindow);
std::string AddSpawnPointers(LuaSpell* spell, bool first_cast, bool precast = false, const char* function = 0, SpellScriptTimer* timer = 0, bool passLuaSpell=false, Spawn* altTarget = 0);
LuaSpell* GetCurrentSpell(lua_State* state, bool needsLock = true);
void RemoveCurrentSpell(lua_State* state, LuaSpell* cur_spell, bool needsLock = true, bool removeCurSpell = true, bool removeSpellScript = true);
bool CallSpellProcess(LuaSpell* spell, int8 num_parameters, std::string functionCalled);
LuaSpell* GetSpell(const char* name, bool use = true);
void UseItemScript(const char* name, lua_State* state, bool val);
void UseSpawnScript(const char* name, lua_State* state, bool val);
void UseZoneScript(const char* name, lua_State* state, bool val);
void UseRegionScript(const char* name, lua_State* state, bool val);
lua_State* GetItemScript(const char* name, bool create_new = true, bool use = false);
lua_State* GetSpawnScript(const char* name, bool create_new = true, bool use = false);
lua_State* GetZoneScript(const char* name, bool create_new = true, bool use = false);
lua_State* GetRegionScript(const char* name, bool create_new = true, bool use = false);
LuaSpell* GetSpellScript(const char* name, bool create_new = true, bool use = true);
LuaSpell* CreateSpellScript(const char* name, lua_State* existState);
Quest* LoadQuest(int32 id, const char* name, const char* type, const char* zone, int8 level, const char* description, char* script_name);
const char* GetScriptName(lua_State* state);
void RemoveSpawnScript(const char* name);
bool RunItemScript(string script_name, const char* function_name, Item* item, Spawn* spawn = 0, Spawn* target = 0, sint64* returnValue = 0);
bool RunItemScriptWithReturnString(string script_name, const char* function_name, Item* item, Spawn* spawn = 0, std::string* returnValue = 0);
bool CallItemScript(lua_State* state, int8 num_parameters, std::string* returnValue = 0);
bool CallItemScript(lua_State* state, int8 num_parameters, sint64* returnValue = 0);
bool RunSpawnScript(string script_name, const char* function_name, Spawn* npc, Spawn* spawn = 0, const char* message = 0, bool is_door_open = false, sint32 input_value = 0, sint32* return_value = 0);
bool CallSpawnScript(lua_State* state, int8 num_parameters);
bool RunZoneScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn = 0, int32 int32_arg1 = 0, const char* str_arg1 = 0, Spawn* spawn_arg1 = 0, int32 int32_arg2 = 0, const char* str_arg2 = 0, Spawn* spawn_arg2 = 0);
bool RunZoneScriptWithReturn(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn, int32 int32_arg1, int32 int32_arg2, int32 int32_arg3, int32* returnValue = 0);
bool CallScriptInt32(lua_State* state, int8 num_parameters, int32* returnValue = 0);
bool CallScriptSInt32(lua_State* state, int8 num_parameters, sint32* returnValue = 0);
bool RunRegionScript(string script_name, const char* function_name, ZoneServer* zone, Spawn* spawn = 0, sint32 int32_arg1 = 0, int32* returnValue = 0);
bool CallRegionScript(lua_State* state, int8 num_parameters, int32* returnValue);
void ResetFunctionStack(lua_State* state);
void DestroySpells();
void DestroySpawnScripts();
void DestroyItemScripts();
void DestroyQuests(bool reload = false);
void DestroyZoneScripts();
void DestroyRegionScripts();
void SimpleLogError(const char* error);
void LogError(const char* error, ...);
bool CallQuestFunction(Quest* quest, const char* function, Spawn* player, int32 step_id = 0xFFFFFFFF, int32* returnValue = 0);
void RemoveDebugClients(Client* client);
void UpdateDebugClients(Client* client);
void ProcessErrorMessage(const char* message);
map<Client*, int32> GetDebugClients(){ return debug_clients; }
void AddUserDataPtr(LUAUserData* data, void* data_ptr = 0);
void DeleteUserDataPtrs(bool all);
void DeletePendingSpells(bool all);
void DeletePendingSpell(LuaSpell* spell);
Mutex* GetSpawnScriptMutex(const char* name);
Mutex* GetItemScriptMutex(const char* name);
Mutex* GetZoneScriptMutex(const char* name);
Mutex* GetRegionScriptMutex(const char* name);
Mutex* GetSpellScriptMutex(const char* name);
Mutex* GetQuestMutex(Quest* quest);
void SetLuaSystemReloading(bool val) { lua_system_reloading = val; }
bool IsLuaSystemReloading() { return lua_system_reloading; }
void AddPendingSpellDelete(LuaSpell* spell);
void AddCustomSpell(LuaSpell* spell);
void RemoveCustomSpell(int32 id);
void FindCustomSpellLock() { MCustomSpell.readlock(); }
void FindCustomSpellUnlock() { MCustomSpell.releasereadlock(); }
LuaSpell* FindCustomSpell(int32 id);
int32 GetFreeCustomSpellID();
void SetLuaUserDataStale(void* ptr);
private:
bool shutting_down;
bool lua_system_reloading;
map<LuaSpell*, int32> spells_pending_delete;
Timer* user_data_timer;
Timer* spell_delete_timer;
map<LUAUserData*, int32> user_data;
map<void*, LUAUserData*> user_data_ptr;
map<Client*, int32> debug_clients;
map<lua_State*, LuaSpell*> current_spells;
vector<string>* GetDirectoryListing(const char* directory);
lua_State* LoadLuaFile(const char* name);
void RegisterFunctions(lua_State* state);
map<lua_State*, string> inverse_spells;
map<int32, Quest*> quests;
map<int32, lua_State*> quest_states;
map<string, map<lua_State*, bool> > item_scripts;
map<string, map<lua_State*, bool> > spawn_scripts;
map<string, map<lua_State*, bool> > zone_scripts;
map<string, map<lua_State*, bool> > region_scripts;
map<string, map<lua_State*, LuaSpell*> > spell_scripts;
map<int32, LuaSpell*> custom_spells;
std::deque<int32> custom_free_spell_ids;
map<lua_State*, string> item_inverse_scripts;
map<lua_State*, string> spawn_inverse_scripts;
map<lua_State*, string> zone_inverse_scripts;
map<lua_State*, string> region_inverse_scripts;
map<string, Mutex*> item_scripts_mutex;
map<string, Mutex*> spawn_scripts_mutex;
map<string, Mutex*> zone_scripts_mutex;
map<int32, Mutex*> quests_mutex;
map<string, Mutex*> region_scripts_mutex;
map<string, Mutex*> spell_scripts_mutex;
Mutex MDebugClients;
Mutex MSpells;
Mutex MSpawnScripts;
Mutex MItemScripts;
Mutex MZoneScripts;
Mutex MQuests;
Mutex MLUAMain;
Mutex MSpellDelete;
Mutex MCustomSpell;
Mutex MRegionScripts;
Mutex MSpellScripts;
mutable std::shared_mutex MLUAUserData;
};
#endif

View File

@ -0,0 +1,201 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MUTEXHELPER_H
#define MUTEXHELPER_H
#include "../common/timer.hpp"
#include <list>
#include <map>
template<typename T>
class IsPointer {
public:
static bool ValidPointer(T key){
return false;
}
static void Delete(T key){
}
};
class Locker{
public:
Locker(){
#ifdef WIN32
InitializeCriticalSection(&CSMutex);
#else
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
pthread_mutex_init(&CSMutex, &attr);
pthread_mutexattr_destroy(&attr);
#endif
}
Locker(const Locker& locker){
#ifdef WIN32
InitializeCriticalSection(&CSMutex);
#else
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE_NP);
pthread_mutex_init(&CSMutex, &attr);
pthread_mutexattr_destroy(&attr);
#endif
}
~Locker(){
#ifdef WIN32
DeleteCriticalSection(&CSMutex);
#else
// pthread_mutex_destroy(&CSMutex);
#endif
}
void lock(){
#ifdef WIN32
EnterCriticalSection(&CSMutex);
#else
pthread_mutex_lock(&CSMutex);
#endif
}
void unlock(){
#ifdef WIN32
LeaveCriticalSection(&CSMutex);
#else
pthread_mutex_unlock(&CSMutex);
#endif
}
private:
#ifdef WIN32
CRITICAL_SECTION CSMutex;
#else
pthread_mutex_t CSMutex;
#endif
};
template<typename T>
class IsPointer<T*> {
public:
static bool ValidPointer(T* key){
return true;
}
static void Delete(T* key){
if(key){
delete key;
key = 0;
}
}
};
template <typename KeyT, typename ValueT>
class DeleteData{
public:
void SetData(int type, KeyT key, ValueT value, unsigned int time){
this->type = type;
this->key = key;
this->value = value;
this->time = time;
}
void DeleteKey(){
IsPointer<KeyT>::Delete(key);
}
void DeleteValue(){
IsPointer<ValueT>::Delete(value);
}
unsigned int GetTime(){
return time;
}
int GetType(){
return type;
}
private:
int type;
KeyT key;
ValueT value;
unsigned int time;
};
template<typename T>
class HandleDeletes {
public:
HandleDeletes(){
access_count = 0;
next_delete_attempt = 0;
changing = false;
has_pending_deletes = false;
}
~HandleDeletes(){
CheckDeletes(true);
}
void AddPendingDelete(T value, unsigned int time){
if(IsPointer<T>::ValidPointer(value)){
while(changing){
Sleep(1);
}
++access_count;
pending_deletes[value] = time;
has_pending_deletes = true;
--access_count;
}
}
void CheckDeletes(bool force = false){
while(changing){
Sleep(1);
}
if(has_pending_deletes && (force || (Timer::GetCurrentTime2() > next_delete_attempt && access_count == 0))){
changing = true;
while(access_count > 0){
Sleep(1);
}
++access_count;
next_delete_attempt = Timer::GetCurrentTime2();
std::list<T> deletes;
typename std::map<T, unsigned int>::iterator pending_delete_itr;
for(pending_delete_itr = pending_deletes.begin(); pending_delete_itr != pending_deletes.end(); pending_delete_itr++){
if(force || next_delete_attempt >= pending_delete_itr->second)
deletes.push_back(pending_delete_itr->first);
}
if(deletes.size() > 0){
typename std::list<T>::iterator delete_itr;
for(delete_itr = deletes.begin(); delete_itr != deletes.end(); delete_itr++){
IsPointer<T>::Delete(*delete_itr);
pending_deletes.erase(*delete_itr);
}
has_pending_deletes = (pending_deletes.size() > 0);
}
next_delete_attempt += 1000;
--access_count;
changing = false;
}
}
private:
volatile bool changing;
volatile int access_count;
volatile unsigned int next_delete_attempt;
volatile bool has_pending_deletes;
std::map<T, unsigned int> pending_deletes;
};
#endif

277
old/WorldServer/MutexList.h Normal file
View File

@ -0,0 +1,277 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MUTEXLIST_H
#define MUTEXLIST_H
#include <list>
#include "MutexHelper.h"
#define MUTEXLIST_PENDING_ADD 1
#define MUTEXLIST_PENDING_REMOVE 2
#define MUTEXLIST_PENDING_DELETE 3
template <typename T>
class MutexList{
public:
MutexList(){
pending_changing = false;
has_pending_data = false;
pending_clear = false;
changing = false;
access_count = 0;
access_pending = 0;
}
MutexList(const MutexList& list){
pending_changing = false;
has_pending_data = false;
pending_clear = false;
changing = false;
access_count = 0;
access_pending = 0;
/*if(list.has_pending_data)
pending_data = list.pending_data;
current_data = list.current_data; */
}
~MutexList(){
while(!update(true)){
Sleep(1);
}
}
class iterator {
private:
typename std::list<T>::iterator itr; // Current element
MutexList<T>* list;
bool first_itr;
public:
iterator(){
}
iterator(MutexList<T>* list){
if(list){
this->list = list;
list->update();
this->list->AddAccess();
first_itr = true;
itr = list->current_data.begin();
if(itr != list->current_data.end())
value = *itr;
}
else
this->list = 0;
}
~iterator(){
if(list)
list->RemoveAccess();
}
bool HasNext(){
return itr != list->current_data.end();
}
bool Next(){
if(list->pending_clear)
return false;
if(first_itr)
first_itr = false;
else
itr++;
if(itr != list->current_data.end()){
value = *itr;
if(list->PendingContains(value)) //pending delete
return Next();
return true;
}
return false;
}
iterator* operator->() {
return this;
}
T value;
};
void SetChanging(){
ChangingLock.lock();
changing = true;
ChangingLock.unlock();
}
void SetNotChanging(){
ChangingLock.lock();
changing = false;
ChangingLock.unlock();
}
void AddAccess(){
AccessLock.lock();
++access_count;
AccessLock.unlock();
}
void RemoveAccess(){
AccessLock.lock();
--access_count;
AccessLock.unlock();
}
unsigned int size(bool include_pending = false){
if(include_pending){
update();
return current_data.size() + pending_data.size();
}
return current_data.size();
}
iterator begin(){
return iterator(this);
}
void clear(bool erase_all = false){
pending_clear = true;
if(erase_all){
AddAccess();
PendingLock.lock();
typename std::list<T>::iterator itr;
for(itr = current_data.begin(); itr != current_data.end(); itr++){
RemoveData(*itr);
}
PendingLock.unlock();
RemoveAccess();
}
update();
}
bool PendingContains(T key){
if(!has_pending_data)
return false;
bool ret = false;
PendingLock.lock();
ret = (pending_data.count(key) > 0 && pending_data[key] == false);
PendingLock.unlock();
return ret;
}
unsigned int count(T key){
unsigned int ret = 0;
while(changing){
Sleep(1);
}
AddAccess();
bool retry = false;
if(!changing){
typename std::list<T>::iterator iter;
for(iter = current_data.begin(); iter != current_data.end(); iter++){
if(*iter == key)
ret++;
}
}
else
retry = true;
RemoveAccess();
if(retry)
return count(key); //only occurs whenever we change to changing state at the same time as a reading state
return ret;
}
void RemoveData(T key, int32 erase_time = 0){
handle_deletes.AddPendingDelete(key, Timer::GetCurrentTime2() + erase_time);
}
void Remove(T key, bool erase = false, int32 erase_time = 0){
while(changing){
Sleep(1);
}
AddAccess();
PendingLock.lock();
pending_data[key] = false;
PendingLock.unlock();
if(erase)
RemoveData(key, erase_time);
has_pending_data = true;
RemoveAccess();
update();
}
void Add(T key){
if(count(key) > 0)
return;
while(changing){
Sleep(1);
}
AddAccess();
PendingLock.lock();
pending_data[key] = true;
PendingLock.unlock();
has_pending_data = true;
RemoveAccess();
update();
}
private:
bool update(bool force = false){
//if(access_count > 5)
// cout << "Possible error.\n";
while(changing){
Sleep(1);
}
if(pending_clear && access_count == 0){
SetChanging();
while(access_count > 0){
Sleep(1);
}
AddAccess();
PendingLock.lock();
current_data.clear();
has_pending_data = (pending_data.size() > 0);
PendingLock.unlock();
pending_clear = false;
RemoveAccess();
SetNotChanging();
}
if(!pending_clear && has_pending_data && access_count == 0){
SetChanging();
while(access_count > 0){
Sleep(1);
}
AddAccess();
PendingLock.lock();
typename std::map<T, bool>::iterator pending_itr;
for(pending_itr = pending_data.begin(); pending_itr != pending_data.end(); pending_itr++){
if(pending_itr->second)
current_data.push_back(pending_itr->first);
else
current_data.remove(pending_itr->first);
}
pending_data.clear();
PendingLock.unlock();
has_pending_data = false;
RemoveAccess();
SetNotChanging();
}
handle_deletes.CheckDeletes(force);
return !pending_clear && !has_pending_data;
}
Locker PendingLock;
Locker AccessLock;
Locker ChangingLock;
volatile int access_count;
std::list<T> current_data;
std::map<T, bool> pending_data;
HandleDeletes<T> handle_deletes;
volatile int access_pending;
volatile bool pending_changing;
volatile bool changing;
volatile bool has_pending_data;
volatile bool pending_clear;
};
#endif

304
old/WorldServer/MutexMap.h Normal file
View File

@ -0,0 +1,304 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MUTEXMAP_H
#define MUTEXMAP_H
#include <map>
#include "MutexHelper.h"
#define MUTEXMAP_DELETE_TYPE_KEY 1
#define MUTEXMAP_DELETE_TYPE_VALUE 2
template <typename KeyT, typename ValueT>
class MutexMap{
public:
MutexMap(){
has_pending_data = false;
pending_clear = false;
changing = false;
access_count = 0;
has_pending_deletes = false;
next_delete_attempt = 0;
delete_all = false;
}
~MutexMap(){
update(true);
PendingLock.lock();
pending_add.clear();
pending_remove.clear();
PendingLock.unlock();
}
class iterator {
private:
typename std::map<KeyT, ValueT>::iterator itr; // Current element
MutexMap* map;
bool first_itr;
public:
iterator(){
}
iterator(MutexMap* map){
this->map = map;
map->update();
map->SetChanging();
this->map->AddAccess();
map->SetNotChanging();
first_itr = true;
itr = map->current_data.begin();
if(itr != map->current_data.end()){
first = itr->first;
second = itr->second;
}
}
~iterator(){
map->RemoveAccess();
}
bool HasNext(){
return itr != map->current_data.end();
}
bool Next(){
if(map->pending_clear)
return false;
if(first_itr)
first_itr = false;
else
itr++;
if(itr != map->current_data.end()){
first = itr->first;
second = itr->second;
map->PendingLock.lock();
if(map->pending_remove.count(first) > 0){
map->PendingLock.unlock();
return Next();
}
map->PendingLock.unlock();
return true;
}
return false;
}
iterator* operator->() {
return this;
}
KeyT first;
ValueT second;
};
int count(KeyT key, bool include_pending = false){
while(changing){
Sleep(1);
}
AddAccess();
int ret = current_data.count(key);
if(include_pending){
PendingLock.lock();
ret += pending_add.count(key);
PendingLock.unlock();
}
RemoveAccess();
return ret;
}
void clear(bool delete_all = false){
pending_clear = true;
if(delete_all){
SetChanging();
while(access_count > 0){
Sleep(1);
}
AddAccess();
PendingLock.lock();
typename std::map<KeyT, ValueT>::iterator itr;
for(itr = current_data.begin(); itr != current_data.end(); itr++){
deleteData(itr->first, MUTEXMAP_DELETE_TYPE_VALUE);
}
PendingLock.unlock();
RemoveAccess();
SetNotChanging();
}
update();
}
unsigned int size(bool include_pending = false){
if(include_pending)
return current_data.size() + pending_add.size();
return current_data.size();
}
void deleteData(KeyT key, int8 type, int32 erase_time = 0){
DeleteData<KeyT, ValueT>* del = new DeleteData<KeyT, ValueT>();
del->SetData(type, key, current_data[key], Timer::GetCurrentTime2() + erase_time);
pending_deletes[del] = true;
has_pending_deletes = true;
}
void erase(KeyT key, bool erase_key = false, bool erase_value = false, int32 erase_time = 0){
while(changing){
Sleep(1);
}
AddAccess();
if(current_data.count(key) != 0){
PendingLock.lock();
pending_remove[key] = true;
if(erase_key || erase_value){
int type = 0;
if(erase_key)
type = MUTEXMAP_DELETE_TYPE_KEY;
if(erase_value)
type += MUTEXMAP_DELETE_TYPE_VALUE;
deleteData(key, type, erase_time);
}
has_pending_data = true;
PendingLock.unlock();
}
RemoveAccess();
update();
}
iterator begin(){
return iterator(this);
}
void Put(KeyT key, ValueT value){
while(changing){
Sleep(1);
}
AddAccess();
PendingLock.lock();
pending_add[key] = value;
has_pending_data = true;
PendingLock.unlock();
RemoveAccess();
update();
}
ValueT& Get(KeyT key){
while(changing){
Sleep(1);
}
AddAccess();
if(current_data.count(key) > 0 || pending_add.count(key) == 0){
RemoveAccess();
return current_data[key];
}
RemoveAccess();
return pending_add[key];
}
private:
void AddAccess(){
AccessLock.lock();
++access_count;
AccessLock.unlock();
}
void RemoveAccess(){
AccessLock.lock();
--access_count;
AccessLock.unlock();
}
void SetChanging(){
ChangingLock.lock();
changing = true;
}
void SetNotChanging(){
changing = false;
ChangingLock.unlock();
}
void update(bool force = false){
if(pending_clear && (force || access_count == 0)){
SetChanging();
while(access_count > 0){
Sleep(1);
}
AddAccess();
PendingLock.lock();
current_data.clear();
has_pending_data = (pending_add.size() > 0 || pending_remove.size() > 0);
pending_clear = false;
PendingLock.unlock();
RemoveAccess();
SetNotChanging();
}
if(!pending_clear && has_pending_data && (force || access_count == 0)){
SetChanging();
while(access_count > 0){
Sleep(1);
}
AddAccess();
PendingLock.lock();
typename std::map<KeyT, bool>::iterator remove_itr;
for(remove_itr = pending_remove.begin(); remove_itr != pending_remove.end(); remove_itr++){
current_data.erase(remove_itr->first);
}
typename std::map<KeyT, ValueT>::iterator add_itr;
for(add_itr = pending_add.begin(); add_itr != pending_add.end(); add_itr++){
current_data[add_itr->first] = add_itr->second;
}
pending_add.clear();
pending_remove.clear();
has_pending_data = false;
PendingLock.unlock();
RemoveAccess();
SetNotChanging();
}
if(has_pending_deletes && (force || (Timer::GetCurrentTime2() > next_delete_attempt && access_count == 0))){
SetChanging();
while(access_count > 0){
Sleep(1);
}
AddAccess();
PendingLock.lock();
unsigned int time = Timer::GetCurrentTime2();
typename std::list<DeleteData<KeyT, ValueT>*> deleteData;
typename std::map<DeleteData<KeyT, ValueT>*, bool>::iterator remove_itr;
for(remove_itr = pending_deletes.begin(); remove_itr != pending_deletes.end(); remove_itr++){
if(force || time >= remove_itr->first->GetTime())
deleteData.push_back(remove_itr->first);
}
DeleteData<KeyT, ValueT>* data = 0;
typename std::list<DeleteData<KeyT, ValueT>*>::iterator remove_data_itr;
for(remove_data_itr = deleteData.begin(); remove_data_itr != deleteData.end(); remove_data_itr++){
data = *remove_data_itr;
if((data->GetType() & MUTEXMAP_DELETE_TYPE_KEY) == MUTEXMAP_DELETE_TYPE_KEY){
data->DeleteKey();
}
if((data->GetType() & MUTEXMAP_DELETE_TYPE_VALUE) == MUTEXMAP_DELETE_TYPE_VALUE){
data->DeleteValue();
}
pending_deletes.erase(data);
delete data;
}
next_delete_attempt = Timer::GetCurrentTime2() + 1000;
PendingLock.unlock();
RemoveAccess();
SetNotChanging();
has_pending_deletes = (pending_deletes.size() > 0);
}
}
Locker PendingLock;
Locker AccessLock;
Locker ChangingLock;
std::map<KeyT, ValueT> current_data;
std::map<KeyT, ValueT> pending_add;
std::map<DeleteData<KeyT, ValueT>*, bool > pending_deletes;
std::map<KeyT, bool> pending_remove;
volatile unsigned int next_delete_attempt;
volatile int access_count;
volatile bool delete_all;
volatile bool changing;
volatile bool has_pending_data;
volatile bool has_pending_deletes;
volatile bool pending_clear;
};
#endif

View File

@ -0,0 +1,202 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef MUTEXVECTOR_H
#define MUTEXVECTOR_H
#include <vector>
#include "MutexHelper.h"
#define MUTEXVECTOR_PENDING_ADD 1
#define MUTEXVECTOR_PENDING_REMOVE 2
#define MUTEXVECTOR_PENDING_DELETE 3
template <typename T>
class MutexVector{
public:
MutexVector(){
has_pending_data = false;
pending_clear = false;
changing = false;
access_count = 0;
}
~MutexVector(){
}
class iterator {
private:
typename std::vector<T>::iterator itr; // Current element
MutexVector<T>* vector;
bool first_itr;
public:
iterator(){
}
iterator(MutexVector<T>* vector){
if(vector){
this->vector = vector;
vector->update();
++this->vector->access_count;
first_itr = true;
itr = vector->current_data.begin();
if(itr != vector->current_data.end())
value = *itr;
}
else
this->vector = 0;
}
~iterator(){
if(vector)
--vector->access_count;
}
bool HasNext(){
return itr != vector->current_data.end();
}
bool Next(){
if(vector->pending_clear)
return false;
if(first_itr)
first_itr = false;
else
itr++;
if(itr != vector->current_data.end()){
value = *itr;
if(vector->pending_data.count(value) > 0 && vector->pending_data[value] == false) //pending delete
return Next();
return true;
}
return false;
}
iterator* operator->() {
return this;
}
T value;
};
void update(){
//if(access_count > 5)
// cout << "Possible error.\n";
while(changing){
Sleep(1);
}
if(pending_clear && access_count == 0){
changing = true;
while(access_count > 0){
Sleep(1);
}
++access_count;
current_data.clear();
has_pending_data = (pending_data.size() > 0);
pending_clear = false;
--access_count;
changing = false;
}
if(!pending_clear && has_pending_data && access_count == 0){
changing = true;
while(access_count > 0){
Sleep(1);
}
++access_count;
typename std::map<T, bool>::iterator pending_itr;
for(pending_itr = pending_data.begin(); pending_itr != pending_data.end(); pending_itr++){
if(pending_itr->second)
current_data.push_back(pending_itr->first);
else{
typename std::vector<T>::iterator data_itr;
for(data_itr = current_data.begin(); data_itr != current_data.end(); data_itr++){
if(*data_itr == pending_itr->first){
current_data.erase(data_itr);
break;
}
}
}
}
pending_data.clear();
has_pending_data = false;
--access_count;
changing = false;
}
handle_deletes.CheckDeletes();
}
unsigned int size(bool include_pending = false){
if(include_pending)
return current_data.size() + pending_data.size();
return current_data.size();
}
iterator begin(){
return iterator(this);
}
void clear(){
pending_clear = true;
update();
}
unsigned int count(T key){
unsigned int ret = 0;
while(changing){
Sleep(1);
}
++access_count;
typename std::list<T>::iterator iter;
for(iter = current_data.begin(); iter != current_data.end(); iter++){
if(*iter == key)
ret++;
}
--access_count;
return ret;
}
void Remove(T key, bool erase = false, unsigned int erase_time = 0){
while(changing){
Sleep(1);
}
++access_count;
pending_data[key] = false;
if(erase)
handle_deletes.AddPendingDelete(key, erase_time);
has_pending_data = true;
--access_count;
update();
}
void Add(T key){
while(changing){
Sleep(1);
}
++access_count;
pending_data[key] = true;
has_pending_data = true;
--access_count;
update();
}
T Get(unsigned int index){
while(changing){
Sleep(1);
}
return current_data[index];
}
private:
volatile int access_count;
std::vector<T> current_data;
std::map<T, bool> pending_data;
HandleDeletes<T> handle_deletes;
volatile bool changing;
volatile bool has_pending_data;
volatile bool pending_clear;
};
#endif

1086
old/WorldServer/NPC.cpp Normal file

File diff suppressed because it is too large Load Diff

217
old/WorldServer/NPC.h Normal file
View File

@ -0,0 +1,217 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __EQ2_NPC__
#define __EQ2_NPC__
#include <atomic>
#include "Entity.h"
#include "MutexMap.h"
#define AI_STRATEGY_BALANCED 1
#define AI_STRATEGY_OFFENSIVE 2
#define AI_STRATEGY_DEFENSIVE 3
// Randomize Appearances
#define RANDOMIZE_GENDER 1
#define RANDOMIZE_RACE 2
#define RANDOMIZE_MODEL_TYPE 4
// Randomize appearance id (spawn_npcs table values)
#define RANDOMIZE_FACIAL_HAIR_TYPE 8 // was RANDOMIZE_FACIAL_HAIR
#define RANDOMIZE_HAIR_TYPE 16 // was RANDOMIZE_HAIR
//#define RANDOMIZE_LEGS_TYPE 32 // spare!
#define RANDOMIZE_WING_TYPE 64
// Randomize parameters (npc_appearances, sInt values)
#define RANDOMIZE_CHEEK_TYPE 128
#define RANDOMIZE_CHIN_TYPE 256
#define RANDOMIZE_EAR_TYPE 512
#define RANDOMIZE_EYE_BROW_TYPE 1024
#define RANDOMIZE_EYE_TYPE 2048
#define RANDOMIZE_LIP_TYPE 4096
#define RANDOMIZE_NOSE_TYPE 8192
// Randomize colors/hues (npc_appearances, RGB values)
#define RANDOMIZE_EYE_COLOR 16384
#define RANDOMIZE_HAIR_COLOR1 32768
#define RANDOMIZE_HAIR_COLOR2 65536
#define RANDOMIZE_HAIR_HIGHLIGHT 131072
#define RANDOMIZE_HAIR_FACE_COLOR 262144 // was RANDOMIZE_FACIAL_HAIR_COLOR
#define RANDOMIZE_HAIR_FACE_HIGHLIGHT_COLOR 524288
#define RANDOMIZE_HAIR_TYPE_COLOR 1048576 // was RANDOMIZE_HAIR_COLOR
#define RANDOMIZE_HAIR_TYPE_HIGHLIGHT_COLOR 2097152
#define RANDOMIZE_SKIN_COLOR 4194304
#define RANDOMIZE_WING_COLOR1 8388608
#define RANDOMIZE_WING_COLOR2 16777216
// All Flags On: 33554431
#define PET_TYPE_COMBAT 1
#define PET_TYPE_CHARMED 2
#define PET_TYPE_DEITY 3
#define PET_TYPE_COSMETIC 4
#define PET_TYPE_DUMBFIRE 5
enum CAST_TYPE {
CAST_ON_SPAWN=0,
CAST_ON_AGGRO=1,
MAX_CAST_TYPES=2
};
class Brain;
class NPCSpell {
public:
NPCSpell() {
}
NPCSpell(NPCSpell* inherit) {
list_id = inherit->list_id;
spell_id = inherit->spell_id;
tier = inherit->tier;
cast_on_spawn = inherit->cast_on_spawn;
cast_on_initial_aggro = inherit->cast_on_initial_aggro;
required_hp_ratio = inherit->required_hp_ratio;
}
int32 list_id;
int32 spell_id;
int8 tier;
bool cast_on_spawn;
bool cast_on_initial_aggro;
sint8 required_hp_ratio;
};
class NPC : public Entity {
public:
NPC();
NPC(NPC* old_npc);
virtual ~NPC();
void Initialize();
EQ2Packet* serialize(Player* player, int16 version);
void SetAppearanceID(int32 id){ appearance_id = id; }
int32 GetAppearanceID(){ return appearance_id; }
bool IsNPC(){ return true; }
void StartRunback(bool reset_hp_on_runback = false);
void InCombat(bool val);
bool HandleUse(Client* client, string type);
void SetRandomize(int32 value) {appearance.randomize = value;}
void AddRandomize(sint32 value) {appearance.randomize += value;}
int32 GetRandomize() {return appearance.randomize;}
bool CheckSameAppearance(string name, int16 id);
void Randomize(NPC* npc, int32 flags);
Skill* GetSkillByName(const char* name, bool check_update = false);
Skill* GetSkillByID(int32 id, bool check_update = false);
int8 GetAttackType();
void SetAIStrategy(int8 strategy);
int8 GetAIStrategy();
void SetPrimarySpellList(int32 id);
int32 GetPrimarySpellList();
void SetSecondarySpellList(int32 id);
int32 GetSecondarySpellList();
void SetPrimarySkillList(int32 id);
int32 GetPrimarySkillList();
void SetSecondarySkillList(int32 id);
int32 GetSecondarySkillList();
void SetEquipmentListID(int32 id);
int32 GetEquipmentListID();
Spell* GetNextSpell(Spawn* target, float distance);
virtual Spell* GetNextBuffSpell(Spawn* target = 0);
void SetAggroRadius(float radius, bool overrideBaseValue = false);
float GetAggroRadius();
float GetBaseAggroRadius() { return base_aggro_radius; }
void SetCastPercentage(int8 percentage);
int8 GetCastPercentage();
void SetSkills(map<string, Skill*>* in_skills);
void SetSpells(vector<NPCSpell*>* in_spells);
void SetRunbackLocation(float x, float y, float z, int32 gridid, bool set_hp_runback = false);
MovementLocation* GetRunbackLocation();
float GetRunbackDistance();
void Runback(float distance=0.0f, bool stopFollowing = true);
void ClearRunback();
virtual bool PauseMovement(int32 period_of_time_ms);
virtual bool IsPauseMovementTimerActive();
void AddSkillBonus(int32 spell_id, int32 skill_id, float value);
virtual void RemoveSkillBonus(int32 spell_id);
virtual void SetZone(ZoneServer* zone, int32 version=0);
void SetMaxPetLevel(int8 val) { m_petMaxLevel = val; }
int8 GetMaxPetLevel() { return m_petMaxLevel; }
void ProcessCombat();
/// <summary>Sets the brain this NPC should use</summary>
/// <param name="brain">The brain this npc should use</param>
void SetBrain(Brain* brain);
/// <summary>Gets the current brain this NPC uses</summary>
/// <returns>The Brain this NPC uses</returns>
::Brain* Brain() { return m_brain; }
bool m_runningBack;
sint16 m_runbackHeadingDir1;
sint16 m_runbackHeadingDir2;
int32 GetShardID() { return m_ShardID; }
void SetShardID(int32 shardid) { m_ShardID = shardid; }
int32 GetShardCharID() { return m_ShardCharID; }
void SetShardCharID(int32 charid) { m_ShardCharID = charid; }
sint64 GetShardCreatedTimestamp() { return m_ShardCreatedTimestamp; }
void SetShardCreatedTimestamp(sint64 timestamp) { m_ShardCreatedTimestamp = timestamp; }
bool HasSpells() { return has_spells; }
std::atomic<bool> m_call_runback;
std::atomic<bool> cast_on_aggro_completed;
private:
MovementLocation* runback;
int8 cast_percentage;
float aggro_radius;
float base_aggro_radius;
Spell* GetNextSpell(float distance, int8 type);
map<string, Skill*>* skills;
vector<NPCSpell*>* spells;
vector<NPCSpell*> cast_on_spells[CAST_TYPE::MAX_CAST_TYPES];
int32 primary_spell_list;
int32 secondary_spell_list;
int32 primary_skill_list;
int32 secondary_skill_list;
int32 equipment_list_id;
int8 attack_type;
int8 ai_strategy;
int32 appearance_id;
int32 npc_id;
MutexMap<int32, SkillBonus*> skill_bonus_list;
int8 m_petMaxLevel;
// Because I named the get function Brain() as well we need to use '::' to specify we are refering to
// the brain class and not the function defined above
::Brain* m_brain;
Mutex MBrain;
int32 m_ShardID;
int32 m_ShardCharID;
sint64 m_ShardCreatedTimestamp;
bool has_spells;
};
#endif

920
old/WorldServer/NPC_AI.cpp Normal file
View File

@ -0,0 +1,920 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "NPC_AI.h"
#include "Combat.h"
#include "zoneserver.h"
#include "Spells.h"
#include "../common/log.hpp"
#include "LuaInterface.h"
#include "World.h"
#include "Rules/Rules.h"
extern RuleManager rule_manager;
extern LuaInterface* lua_interface;
extern World world;
/* The NEW AI code */
Brain::Brain(NPC* npc) {
// Set the npc this brain will controll
m_body = npc;
// Set the default time between calls to think to 250 miliseconds (1/4 a second)
m_tick = 250;
m_lastTick = Timer::GetCurrentTime2();
m_spellRecovery = 0;
m_playerInEncounter = false;
// Set up the mutex for the hate list
MHateList.SetName("Brain::m_hatelist");
// Set up the mutex for the encounter list
MEncounter.SetName("Brain::m_encounter");
}
Brain::~Brain() {
}
void Brain::Think() {
if (m_body->IsPet() && m_body->GetOwner() && m_body->GetOwner()->IsPlayer()) {
Player* player = (Player*)m_body->GetOwner();
if(player->GetInfoStruct()->get_pet_id() == 0) {
player->GetInfoStruct()->set_pet_id(player->GetIDWithPlayerSpawn(m_body));
player->SetCharSheetChanged(true);
}
}
// Get the entity this NPC hates the most,
// GetMostHated() will handle dead spawns so no need to check the health in this function
Entity* target = GetMostHated();
// If mezzed, stunned or feared we can't do anything so skip
if (!m_body->IsMezzedOrStunned()) {
// Not mezzed or stunned
// Get the distance to the runback location
float run_back_distance = m_body->GetRunbackDistance();
if (target) {
LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s has %s targeted.", m_body->GetName(), target->GetName());
// NPC has an entity that it hates
// Set the NPC's target to the most hated entity if it is not already.
if (m_body->GetTarget() != target) {
m_body->SetTarget(target);
}
m_body->FaceTarget(target, false);
// target needs to be set before in combat is engaged
// If the NPC is not in combat then put them in combat
if (!m_body->EngagedInCombat()) {
m_body->ClearRunningLocations();
m_body->InCombat(true);
m_body->cast_on_aggro_completed = false;
m_body->GetZone()->CallSpawnScript(m_body, SPAWN_SCRIPT_AGGRO, target);
}
bool breakWaterPursuit = false;
if (m_body->IsWaterCreature() && !m_body->IsFlyingCreature() && !target->InWater())
breakWaterPursuit = true;
// Check to see if the NPC has exceeded the max chase distance
float maxChaseDist = MAX_CHASE_DISTANCE;
if(m_body->GetInfoStruct()->get_max_chase_distance() > 0.0f) {
maxChaseDist = m_body->GetInfoStruct()->get_max_chase_distance();
}
else if(rule_manager.GetZoneRule(m_body->GetZoneID(), R_Combat, MaxChaseDistance)->GetFloat() > 0.0f) {
maxChaseDist = rule_manager.GetZoneRule(m_body->GetZoneID(), R_Combat, MaxChaseDistance)->GetFloat();
}
if (run_back_distance > maxChaseDist || breakWaterPursuit) {
LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "Run back distance is greater then max chase distance, run_back_distance = %f", run_back_distance);
// Over the max chase distance, Check to see if the target is is a client
if (target->IsPlayer() && ((Player*)target)->GetClient())
{
// Target is a client so send encounter break messages
if (m_body->HasSpawnGroup())
((Player*)target)->GetClient()->SimpleMessage(CHANNEL_NARRATIVE, "This encounter will no longer give encounter rewards.");
else
((Player*)target)->GetClient()->Message(CHANNEL_NARRATIVE, "%s is no longer worth any experience or treasure.", m_body->GetName());
}
// Clear the hate list for this NPC
ClearHate();
// Clear the encounter list
ClearEncounter();
}
else {
// Still within max chase distance lets to the combat stuff now
float distance = m_body->GetDistance(target);
if(!m_body->IsCasting() && (!HasRecovered() || !ProcessSpell(target, distance))) {
LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is attempting melee on %s.", m_body->GetName(), target->GetName());
m_body->FaceTarget(target, false);
ProcessMelee(target, distance);
}
}
}
else {
// Nothing in the hate list
bool wasInCombat = m_body->EngagedInCombat();
// Check to see if the NPC is still flagged as in combat for some reason
if (m_body->EngagedInCombat()) {
// If it is set the combat flag to false
m_body->InCombat(false);
// Do not set a players pet to full health once they stop combat
if (!m_body->IsPet() || (m_body->IsPet() && m_body->GetOwner() && !m_body->GetOwner()->IsPlayer()))
m_body->SetHP(m_body->GetTotalHP());
}
CheckBuffs();
// If run back distance is greater then 0 then run back
if(!m_body->EngagedInCombat() && !m_body->IsPauseMovementTimerActive())
{
if (run_back_distance > 1 || (m_body->m_call_runback && !m_body->following)) {
m_body->SetLockedNoLoot(ENCOUNTER_STATE_BROKEN);
m_body->UpdateEncounterState(ENCOUNTER_STATE_BROKEN);
m_body->GetZone()->AddChangedSpawn(m_body);
m_body->Runback(run_back_distance);
m_body->m_call_runback = false;
}
else if (m_body->GetRunbackLocation())
{
switch(m_body->GetRunbackLocation()->stage)
{
case 0:
m_body->GetZone()->movementMgr->StopNavigation((Entity*)m_body);
m_body->ClearRunningLocations();
m_body->SetX(m_body->GetRunbackLocation()->x,false);
m_body->SetZ(m_body->GetRunbackLocation()->z,false);
m_body->SetY(m_body->GetRunbackLocation()->y,false);
m_body->CalculateRunningLocation(true);
m_body->GetRunbackLocation()->stage = 1;
m_body->GetZone()->AddChangedSpawn(m_body);
break;
case 6: // artificially 1500ms per 250ms Think() call
if (m_body->GetRunbackLocation()->gridid > 0)
m_body->SetLocation(m_body->GetRunbackLocation()->gridid);
if(m_body->GetTempActionState() == 0)
m_body->SetTempActionState(-1);
m_body->SetHeading(m_body->m_runbackHeadingDir1,m_body->m_runbackHeadingDir2,false);
if(m_body->GetRunbackLocation()->reset_hp_on_runback)
m_body->SetHP(m_body->GetTotalHP());
m_body->ClearRunback();
if(m_body->GetLockedNoLoot() != ENCOUNTER_STATE_AVAILABLE && m_body->Alive()) {
m_body->SetLockedNoLoot(ENCOUNTER_STATE_AVAILABLE);
m_body->UpdateEncounterState(ENCOUNTER_STATE_AVAILABLE);
}
m_body->GetZone()->AddChangedSpawn(m_body);
break;
default: // captures case 1 up to case 5 to turn around / reset hp
m_body->GetRunbackLocation()->stage++; // artificially delay
break;
}
}
}
// If encounter size is greater then 0 then clear it
if (GetEncounterSize() > 0)
ClearEncounter();
}
}
}
sint32 Brain::GetHate(Entity* entity) {
// We will use this variable to return the value, default to 0
sint32 ret = 0;
// Lock the hate list, not altering it so do a read lock
MHateList.readlock(__FUNCTION__, __LINE__);
// First check to see if the given entity is even in the hate list
if (m_hatelist.count(entity->GetID()) > 0)
// Entity in the hate list so get the hate value for the entity
ret = m_hatelist[entity->GetID()];
// Unlock the hate list
MHateList.releasereadlock(__FUNCTION__, __LINE__);
// return the hate
return ret;
}
void Brain::AddHate(Entity* entity, sint32 hate) {
// do not aggro when running back, despite taking damage
if (m_body->IsNPC() && ((NPC*)m_body)->m_runningBack)
return;
else if (m_body->IsPet() && m_body->IsEntity() && ((Entity*)m_body)->GetOwner() == entity)
return;
if(m_body->IsImmune(IMMUNITY_TYPE_TAUNT))
{
LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is immune to taunt from entity %s.", m_body->GetName(), entity ? entity->GetName() : "(null)");
if(entity && entity->IsPlayer())
((Player*)entity)->GetClient()->GetCurrentZone()->SendDamagePacket((Spawn*)entity, (Spawn*)m_body, DAMAGE_PACKET_TYPE_RANGE_SPELL_DMG, DAMAGE_PACKET_RESULT_IMMUNE, 0, 0, "Hate");
return;
}
// Lock the hate list, we are altering the list so use write lock
MHateList.writelock(__FUNCTION__, __LINE__);
if (m_hatelist.count(entity->GetID()) > 0) {
m_hatelist[entity->GetID()] += hate;
// take into consideration that 0 or negative hate is not valid, we need to properly reset the value
if(m_hatelist[entity->GetID()] < 1) {
m_hatelist[entity->GetID()] = 1;
}
}
else
m_hatelist.insert(std::pair<int32, sint32>(entity->GetID(), hate));
entity->MHatedBy.lock();
if (entity->HatedBy.count(m_body->GetID()) == 0)
entity->HatedBy.insert(m_body->GetID());
entity->MHatedBy.unlock();
// Unlock the list
bool ownerExistsAddHate = false;
if(entity->IsPet() && entity->GetOwner()) {
map<int32, sint32>::iterator itr = m_hatelist.find(entity->GetOwner()->GetID());
if(itr == m_hatelist.end()) {
ownerExistsAddHate = true;
}
}
MHateList.releasewritelock(__FUNCTION__, __LINE__);
if(ownerExistsAddHate) {
AddHate(entity->GetOwner(), 0);
}
}
void Brain::ClearHate() {
// Lock the hate list, we are altering the list so use a write lock
MHateList.writelock(__FUNCTION__, __LINE__);
map<int32, sint32>::iterator itr;
for (itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) {
Spawn* spawn = m_body->GetZone()->GetSpawnByID(itr->first);
if (spawn && spawn->IsEntity())
{
((Entity*)spawn)->MHatedBy.lock();
((Entity*)spawn)->HatedBy.erase(m_body->GetID());
((Entity*)spawn)->MHatedBy.unlock();
}
}
// Clear the list
m_hatelist.clear();
// Unlock the hate list
MHateList.releasewritelock(__FUNCTION__, __LINE__);
}
void Brain::ClearHate(Entity* entity) {
// Lock the hate list, we could potentially modify the list so use write lock
MHateList.writelock(__FUNCTION__, __LINE__);
// Check to see if the given entity is in the hate list
if (m_hatelist.count(entity->GetID()) > 0)
// Erase the entity from the hate list
m_hatelist.erase(entity->GetID());
entity->MHatedBy.lock();
entity->HatedBy.erase(m_body->GetID());
entity->MHatedBy.unlock();
// Unlock the hate list
MHateList.releasewritelock(__FUNCTION__, __LINE__);
}
Entity* Brain::GetMostHated() {
map<int32, sint32>::iterator itr;
int32 ret = 0;
sint32 hate = 0;
// Lock the hate list, not going to alter it so use a read lock
MHateList.readlock(__FUNCTION__, __LINE__);
if (m_hatelist.size() > 0) {
// Loop through the list looking for the entity that this NPC hates the most
for(itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) {
// Compare the hate value for the current iteration to our stored highest value
if(itr->second > hate) {
// New high value store the entity
ret = itr->first;
// Store the value to compare with the rest of the entities
hate = itr->second;
}
}
}
// Unlock the list
MHateList.releasereadlock(__FUNCTION__, __LINE__);
Entity* hated = (Entity*)GetBody()->GetZone()->GetSpawnByID(ret);
// Check the reult to see if it is still alive
if(hated && hated->GetHP() <= 0) {
// Entity we got was dead so remove it from the list
ClearHate(hated);
// Call this function again now that we removed the dead entity
hated = GetMostHated();
}
// Return our result
return hated;
}
sint8 Brain::GetHatePercentage(Entity* entity) {
float percentage = 0.0;
MHateList.readlock(__FUNCTION__, __LINE__);
if (entity && m_hatelist.count(entity->GetID()) > 0 && m_hatelist[entity->GetID()] > 0) {
sint32 total_hate = 0;
map<int32, sint32>::iterator itr;
for (itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++)
total_hate += itr->second;
percentage = m_hatelist[entity->GetID()] / total_hate;
}
MHateList.releasereadlock(__FUNCTION__, __LINE__);
return (sint8)(percentage * 100);
}
void Brain::SendHateList(Client* client) {
MHateList.readlock(__FUNCTION__, __LINE__);
client->Message(CHANNEL_COLOR_YELLOW, "%s's HateList", m_body->GetName());
client->Message(CHANNEL_COLOR_YELLOW, "-------------------");
map<int32, sint32>::iterator itr;
if (m_hatelist.size() > 0) {
// Loop through the list looking for the entity that this NPC hates the most
for(itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) {
Entity* ent = (Entity*)GetBody()->GetZone()->GetSpawnByID(itr->first);
// Compare the hate value for the current iteration to our stored highest value
if(ent) {
client->Message(CHANNEL_COLOR_YELLOW, "%s : %i", ent->GetName(), itr->second);
}
else {
client->Message(CHANNEL_COLOR_YELLOW, "%u (cannot identity spawn id->entity) : %i", itr->first, itr->second);
}
}
}
client->Message(CHANNEL_COLOR_YELLOW, "-------------------");
MHateList.releasereadlock(__FUNCTION__, __LINE__);
}
vector<Entity*>* Brain::GetHateList() {
vector<Entity*>* ret = new vector<Entity*>;
map<int32, sint32>::iterator itr;
// Lock the list
MHateList.readlock(__FUNCTION__, __LINE__);
// Loop over the list storing the values into the new list
for (itr = m_hatelist.begin(); itr != m_hatelist.end(); itr++) {
Entity* ent = (Entity*)GetBody()->GetZone()->GetSpawnByID(itr->first);
if (ent)
ret->push_back(ent);
}
// Unlock the list
MHateList.releasereadlock(__FUNCTION__, __LINE__);
// Return the copy of the list
return ret;
}
void Brain::MoveCloser(Spawn* target) {
if (target && m_body->GetFollowTarget() != target)
m_body->SetFollowTarget(target, rule_manager.GetZoneRule(m_body->GetZoneID(), R_Combat, MaxCombatRange)->GetFloat());
if (m_body->GetFollowTarget() && !m_body->following) {
m_body->CalculateRunningLocation(true);
//m_body->ClearRunningLocations();
m_body->following = true;
}
}
bool Brain::ProcessSpell(Entity* target, float distance) {
if(rand()%100 > m_body->GetCastPercentage() || m_body->IsStifled() || m_body->IsFeared())
return false;
Spell* spell = m_body->GetNextSpell(target, distance);
if(spell){
Spawn* spell_target = 0;
if(spell->GetSpellData()->friendly_spell == 1){
vector<Spawn*>* group = m_body->GetSpawnGroup();
if(group && group->size() > 0){
vector<Spawn*>::iterator itr;
for(itr = group->begin(); itr != group->end(); itr++){
if((!spell_target && (*itr)->GetHP() > 0 && (*itr)->GetHP() < (*itr)->GetTotalHP()) || (spell_target && (*itr)->GetHP() > 0 && spell_target->GetHP() > (*itr)->GetHP()))
spell_target = *itr;
}
}
if(!spell_target)
spell_target = m_body;
safe_delete(group);
}
else
spell_target = target;
BrainCastSpell(spell, spell_target, false);
return true;
}
return false;
}
bool Brain::BrainCastSpell(Spell* spell, Spawn* cast_on, bool calculate_run_loc) {
if (spell) {
if(calculate_run_loc) {
m_body->CalculateRunningLocation(true);
}
m_body->GetZone()->ProcessSpell(spell, m_body, cast_on);
m_spellRecovery = (int32)(Timer::GetCurrentTime2() + (spell->GetSpellData()->cast_time * 10) + (spell->GetSpellData()->recovery * 10) + 2000);
return true;
}
return false;
}
bool Brain::CheckBuffs() {
if (!m_body->GetZone()->GetSpellProcess() || m_body->EngagedInCombat() || m_body->IsCasting() || m_body->IsMezzedOrStunned() || !m_body->Alive() || m_body->IsStifled() || !HasRecovered())
return false;
Spell* spell = m_body->GetNextBuffSpell(m_body);
bool casted_on = false;
if(!(casted_on = BrainCastSpell(spell, m_body)) && m_body->IsNPC() && ((NPC*)m_body)->HasSpells()) {
Spawn* target = nullptr;
vector<Spawn*>* group = m_body->GetSpawnGroup();
if(group && group->size() > 0){
vector<Spawn*>::iterator itr;
for(itr = group->begin(); itr != group->end(); itr++){
Spawn* spawn = (*itr);
if(spawn->IsEntity() && spawn != m_body) {
if(target) {
Spell* spell = m_body->GetNextBuffSpell(spawn);
SpellEffects* se = ((Entity*)spawn)->GetSpellEffect(spell->GetSpellData()->id);
if(!se && BrainCastSpell(spell, spawn)) {
casted_on = true;
break;
}
}
}
}
}
safe_delete(group);
}
return casted_on;
}
void Brain::ProcessMelee(Entity* target, float distance) {
if(distance > rule_manager.GetZoneRule(m_body->GetZoneID(), R_Combat, MaxCombatRange)->GetFloat())
MoveCloser((Spawn*)target);
else {
if (target) {
LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is within melee range of %s.", m_body->GetName(), target->GetName());
if (m_body->AttackAllowed(target)) {
LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is allowed to attack %s.", m_body->GetName(), target->GetName());
if (m_body->PrimaryWeaponReady() && !m_body->IsDazed() && !m_body->IsFeared()) {
LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s swings its primary weapon at %s.", m_body->GetName(), target->GetName());
m_body->SetPrimaryLastAttackTime(Timer::GetCurrentTime2());
m_body->MeleeAttack(target, distance, true);
m_body->GetZone()->CallSpawnScript(m_body, SPAWN_SCRIPT_AUTO_ATTACK_TICK, target);
}
if (m_body->SecondaryWeaponReady() && !m_body->IsDazed()) {
m_body->SetSecondaryLastAttackTime(Timer::GetCurrentTime2());
m_body->MeleeAttack(target, distance, false);
}
}
}
}
}
bool Brain::HasRecovered() {
if(m_spellRecovery > Timer::GetCurrentTime2())
return false;
m_spellRecovery = 0;
return true;
}
void Brain::AddToEncounter(Entity* entity) {
// If player pet then set the entity to the pets owner
if (entity->IsPet() && entity->GetOwner() && !entity->IsBot()) {
MEncounter.writelock(__FUNCTION__, __LINE__);
bool success = AddToEncounter(entity->GetID());
MEncounter.releasewritelock(__FUNCTION__, __LINE__);
if(!success)
return;
entity = entity->GetOwner();
}
else if(entity->HasPet() && entity->GetPet()) {
MEncounter.writelock(__FUNCTION__, __LINE__);
bool success = AddToEncounter(entity->GetPet()->GetID());
MEncounter.releasewritelock(__FUNCTION__, __LINE__);
if(!success)
return;
}
// If player or bot then get the group
int32 group_id = 0;
if (entity->IsPlayer() || entity->IsBot()) {
m_playerInEncounter = true;
if (entity->GetGroupMemberInfo())
group_id = entity->GetGroupMemberInfo()->group_id;
}
// Insert the entity into the encounter list, if there is a group add all group members as well
// TODO: add raid members
MEncounter.writelock(__FUNCTION__, __LINE__);
if (group_id > 0) {
world.GetGroupManager()->GroupLock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>::iterator itr;
PlayerGroup* group = world.GetGroupManager()->GetGroup(group_id);
if (group)
{
std::vector<int32> raidGroups;
group->GetRaidGroups(&raidGroups);
if(raidGroups.size() < 1)
raidGroups.push_back(group_id);
std::vector<int32>::iterator group_itr;
for(group_itr = raidGroups.begin(); group_itr != raidGroups.end(); group_itr++) {
group = world.GetGroupManager()->GetGroup((*group_itr));
if(group) {
group->MGroupMembers.readlock(__FUNCTION__, __LINE__);
deque<GroupMemberInfo*>* members = group->GetMembers();
for (itr = members->begin(); itr != members->end(); itr++) {
if ((*itr)->member)
{
bool success = AddToEncounter((*itr)->member->GetID());
if((*itr)->client && success) {
m_encounter_playerlist.insert(make_pair((*itr)->client->GetPlayer()->GetCharacterID(), (*itr)->client->GetPlayer()->GetID()));
}
}
}
group->MGroupMembers.releasereadlock(__FUNCTION__, __LINE__);
}
}
}
world.GetGroupManager()->ReleaseGroupLock(__FUNCTION__, __LINE__);
}
else {
bool success = AddToEncounter(entity->GetID());
if (success && entity->IsPlayer())
{
Player* plyr = (Player*)entity;
m_encounter_playerlist.insert(make_pair(plyr->GetCharacterID(), entity->GetID()));
}
}
MEncounter.releasewritelock(__FUNCTION__, __LINE__);
}
bool Brain::CheckLootAllowed(Entity* entity) {
bool ret = false;
vector<int32>::iterator itr;
if (m_body)
{
if ((m_body->GetLootMethod() != GroupLootMethod::METHOD_LOTTO && m_body->GetLootMethod() != GroupLootMethod::METHOD_NEED_BEFORE_GREED) && m_body->GetLooterSpawnID() > 0 && m_body->GetLooterSpawnID() != entity->GetID()) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: CheckLootAllowed failed, looter spawn id %u does not match received %s(%u)", GetBody()->GetName(), m_body->GetLooterSpawnID(), entity->GetName(), entity->GetID());
return false;
}
if (rule_manager.GetZoneRule(m_body->GetZoneID(), R_Loot, AllowChestUnlockByDropTime)->GetInt8()
&& m_body->GetChestDropTime() > 0 && Timer::GetCurrentTime2() >= m_body->GetChestDropTime() + (rule_manager.GetZoneRule(m_body->GetZoneID(), R_Loot, ChestUnlockedTimeDrop)->GetInt32() * 1000)) {
return true;
}
if (rule_manager.GetZoneRule(m_body->GetZoneID(), R_Loot, AllowChestUnlockByTrapTime)->GetInt8()
&& m_body->GetTrapOpenedTime() > 0 && Timer::GetCurrentTime2() >= m_body->GetChestDropTime() + (rule_manager.GetZoneRule(m_body->GetZoneID(), R_Loot, ChestUnlockedTimeTrap)->GetInt32() * 1000)) {
return true;
}
if ((m_body->GetLootMethod() == GroupLootMethod::METHOD_LOTTO || m_body->GetLootMethod() == GroupLootMethod::METHOD_NEED_BEFORE_GREED) && m_body->HasSpawnLootWindowCompleted(entity->GetID())) {
LogWrite(LOOT__INFO, 0, "Loot", "%s: CheckLootAllowed failed, looter %s(%u) has already completed their lotto selections.", GetBody()->GetName(), entity->GetName(), entity->GetID());
return false;
}
}
// Check the encounter list to see if the given entity is in it, if so return true.
MEncounter.readlock(__FUNCTION__, __LINE__);
if (entity->IsPlayer())
{
Player* plyr = (Player*)entity;
map<int32, int32>::iterator itr = m_encounter_playerlist.find(plyr->GetCharacterID());
if (itr != m_encounter_playerlist.end())
{
MEncounter.releasereadlock(__FUNCTION__, __LINE__);
return true;
}
MEncounter.releasereadlock(__FUNCTION__, __LINE__);
return false;
}
for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) {
if ((*itr) == entity->GetID()) {
// found the entity in the encounter list, set return value to true and break the loop
ret = true;
break;
}
}
MEncounter.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
int8 Brain::GetEncounterSize() {
int8 ret = 0;
MEncounter.readlock(__FUNCTION__, __LINE__);
ret = (int8)m_encounter.size();
MEncounter.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
vector<int32>* Brain::GetEncounter() {
vector<int32>* ret = new vector<int32>;
vector<int32>::iterator itr;
// Lock the list
MEncounter.readlock(__FUNCTION__, __LINE__);
// Loop over the list storing the values into the new list
for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++)
ret->push_back(*itr);
// Unlock the list
MEncounter.releasereadlock(__FUNCTION__, __LINE__);
// Return the copy of the list
return ret;
}
bool Brain::IsPlayerInEncounter(int32 char_id) {
bool ret = false;
MEncounter.readlock(__FUNCTION__, __LINE__);
std::map<int32,int32>::iterator itr = m_encounter_playerlist.find(char_id);
if(itr != m_encounter_playerlist.end()) {
ret = true;
}
MEncounter.releasereadlock(__FUNCTION__, __LINE__);
return ret;
}
bool Brain::IsEntityInEncounter(int32 id, bool skip_read_lock) {
bool ret = false;
if(!skip_read_lock) {
MEncounter.readlock(__FUNCTION__, __LINE__);
}
vector<int32>::iterator itr;
for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) {
if ((*itr) == id) {
// found the entity in the encounter list, set return value to true and break the loop
ret = true;
break;
}
}
if(!skip_read_lock) {
MEncounter.releasereadlock(__FUNCTION__, __LINE__);
}
return ret;
}
int32 Brain::CountPlayerBotInEncounter() {
int32 count = 0;
vector<int32>::iterator itr;
MEncounter.readlock(__FUNCTION__, __LINE__);
for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) {
Entity* ent = (Entity*)GetBody()->GetZone()->GetSpawnByID((*itr));
if (ent && (ent->IsPlayer() || ent->IsBot())) {
count++;
}
}
MEncounter.releasereadlock(__FUNCTION__, __LINE__);
return count;
}
bool Brain::AddToEncounter(int32 id) {
if(!IsEntityInEncounter(id, true)) {
m_encounter.push_back(id);
return true;
}
return false;
}
void Brain::ClearEncounter() {
MEncounter.writelock(__FUNCTION__, __LINE__);
if(m_body) {
m_body->RemoveSpells(true);
}
m_encounter.clear();
m_encounter_playerlist.clear();
m_playerInEncounter = false;
MEncounter.releasewritelock(__FUNCTION__, __LINE__);
}
void Brain::SendEncounterList(Client* client) {
client->Message(CHANNEL_COLOR_YELLOW, "%s's EncounterList", m_body->GetName());
client->Message(CHANNEL_COLOR_YELLOW, "-------------------");
vector<int32>::iterator itr;
// Check the encounter list to see if the given entity is in it, if so return true.
MEncounter.readlock(__FUNCTION__, __LINE__);
for (itr = m_encounter.begin(); itr != m_encounter.end(); itr++) {
Entity* ent = (Entity*)GetBody()->GetZone()->GetSpawnByID((*itr));
// Compare the hate value for the current iteration to our stored highest value
if(ent) {
client->Message(CHANNEL_COLOR_YELLOW, "%s", ent->GetName());
}
else {
client->Message(CHANNEL_COLOR_YELLOW, "%u (cannot identity spawn id->entity)", (*itr));
}
}
client->Message(CHANNEL_COLOR_YELLOW, "-------------------");
MEncounter.releasereadlock(__FUNCTION__, __LINE__);
}
/* Example of how to extend the default AI */
CombatPetBrain::CombatPetBrain(NPC* body) : Brain(body) {
// Make sure to have the " : Brain(body)" so it calls the parent class constructor
// to set up the AI properly
}
CombatPetBrain::~CombatPetBrain() {
}
void CombatPetBrain::Think() {
// We are extending the base brain so make sure to call the parent Think() function.
// If we want to override then we could remove Brain::Think()
Brain::Think();
// All this Brain does is make the pet follow its owner, the combat comes from the default brain
if (GetBody()->EngagedInCombat() || !GetBody()->IsPet() || GetBody()->IsMezzedOrStunned())
return;
LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "Pet AI code called for %s", GetBody()->GetName());
// If owner is a player and player has stay set then return out
if (GetBody()->GetOwner() && GetBody()->GetOwner()->IsPlayer() && ((Player*)GetBody()->GetOwner())->GetInfoStruct()->get_pet_movement() == 1)
return;
// Set target to owner
Entity* target = GetBody()->GetOwner();
GetBody()->SetTarget(target);
// Get distance from the owner
float distance = GetBody()->GetDistance(target);
// If out of melee range then move closer
if (distance > rule_manager.GetZoneRule(m_body->GetZoneID(), R_Combat, MaxCombatRange)->GetFloat())
MoveCloser((Spawn*)target);
}
/* Example of how to override the default AI */
NonCombatPetBrain::NonCombatPetBrain(NPC* body) : Brain(body) {
// Make sure to have the " : Brain(body)" so it calls the parent class constructor
// to set up the AI properly
}
NonCombatPetBrain::~NonCombatPetBrain() {
}
void NonCombatPetBrain::Think() {
// All this Brain does is make the pet follow its owner
if (!GetBody()->IsPet() || GetBody()->IsMezzedOrStunned())
return;
LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "Pet AI code called for %s", GetBody()->GetName());
// Set target to owner
Entity* target = GetBody()->GetOwner();
GetBody()->SetTarget(target);
// Get distance from the owner
float distance = GetBody()->GetDistance(target);
// If out of melee range then move closer
if (distance > rule_manager.GetZoneRule(m_body->GetZoneID(), R_Combat, MaxCombatRange)->GetFloat())
MoveCloser((Spawn*)target);
}
BlankBrain::BlankBrain(NPC* body) : Brain(body) {
// Make sure to have the " : Brain(body)" so it calls the parent class constructor
// to set up the AI properly
SetTick(50000);
}
BlankBrain::~BlankBrain() {
}
void BlankBrain::Think() {
}
LuaBrain::LuaBrain(NPC* body) : Brain(body) {
}
LuaBrain::~LuaBrain() {
}
void LuaBrain::Think() {
if (!lua_interface)
return;
const char* script = GetBody()->GetSpawnScript();
if(script) {
if (!lua_interface->RunSpawnScript(script, "Think", GetBody(), GetBody()->GetTarget())) {
lua_interface->LogError("LUA LuaBrain error: was unable to call the Think function in the spawn script (%s)", script);
}
}
else {
LogWrite(NPC_AI__ERROR, 0, "NPC_AI", "Lua brain set on a spawn that doesn't have a script...");
}
}
DumbFirePetBrain::DumbFirePetBrain(NPC* body, Entity* target, int32 expire_time) : Brain(body) {
m_expireTime = Timer::GetCurrentTime2() + expire_time;
AddHate(target, INT_MAX);
}
DumbFirePetBrain::~DumbFirePetBrain() {
}
void DumbFirePetBrain::AddHate(Entity* entity, sint32 hate) {
if (!GetMostHated())
Brain::AddHate(entity, hate);
}
void DumbFirePetBrain::Think() {
Entity* target = GetMostHated();
if (target) {
if (!GetBody()->IsMezzedOrStunned()) {
// Set the NPC's target to the most hated entity if it is not already.
if (GetBody()->GetTarget() != target) {
GetBody()->SetTarget(target);
GetBody()->FaceTarget(target, false);
}
// target needs to be identified before combat setting
// If the NPC is not in combat then put them in combat
if (!GetBody()->EngagedInCombat()) {
//GetBody()->ClearRunningLocations();
GetBody()->CalculateRunningLocation(true);
GetBody()->InCombat(true);
}
float distance = GetBody()->GetDistance(target);
if(GetBody()->CheckLoS(target) && !GetBody()->IsCasting() && (!HasRecovered() || !ProcessSpell(target, distance))) {
LogWrite(NPC_AI__DEBUG, 7, "NPC_AI", "%s is attempting melee on %s.", GetBody()->GetName(), target->GetName());
GetBody()->FaceTarget(target, false);
ProcessMelee(target, distance);
}
}
}
else {
// No hated target or time expired, kill this mob
if (GetBody()->GetHP() > 0) {
GetBody()->KillSpawn(GetBody());
LogWrite(NPC_AI__DEBUG, 7, "NPC AI", "Dumbfire being killed because there is no target.");
}
}
if (Timer::GetCurrentTime2() > m_expireTime) {
if (GetBody()->GetHP() > 0) {
GetBody()->KillSpawn(GetBody());
LogWrite(NPC_AI__DEBUG, 7, "NPC AI", "Dumbfire being killed because timer expired.");
}
}
}

198
old/WorldServer/NPC_AI.h Normal file
View File

@ -0,0 +1,198 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __NPC_AI_H__
#define __NPC_AI_H__
#include "NPC.h"
#include <vector>
#include <map>
using namespace std;
class Brain {
public:
Brain(NPC* npc);
virtual ~Brain();
/// <summary>The main loop for the brain. This will do all the AI work</summary>
virtual void Think();
/* Timer related functions */
/// <summary>Gets the time between calls to Think()</summary>
/// <returns>Time in miliseconds between calls to Think()</returns>
int16 Tick() { return m_tick; }
/// <summary>Sets the time between calls to Think()</summary>
/// <param name="time">Time in miliseconds</param>
void SetTick(int16 time) { m_tick = time; }
/// <summary>Gets the timestamp of the last call to Think()</summary>
/// <returns>Timestamp of the last call to Think()</returns>
int32 LastTick() { return m_lastTick; }
/// <summary>Sets the last tick to the given time</summary>
/// <param name="time">The time to set the last tick to</param>
void SetLastTick(int32 time) { m_lastTick = time; }
/* Hate related functions */
/// <summary>Gets the amount of hate this npc has towards the given entity</summary>
/// <param name="entity">The entity to check</param>
/// <returns>The amount of hate towards the given entity</returns>
sint32 GetHate(Entity* entity);
/// <summary>Add hate for the given entity to this NPC</summary>
/// <param name="entity">The entity we are adding to this NPC's hate list</param>
/// <param name="hate">The amount of hate to add</param>
virtual void AddHate(Entity* entity, sint32 hate);
/// <summary>Completely clears the hate list for this npc</summary>
void ClearHate();
/// <summary>Removes the given entity from this NPC's hate list</summary>
/// <param name="entity">Entity to remove from this NPC's hate list</param>
void ClearHate(Entity* entity);
/// <summary>Get the entity this NPC hates the most</summary>
/// <returns>The entity this NPC hates the most</returns>
Entity* GetMostHated();
/// <summary>Gets a percentage of hate owned by the given entity</summary>
/// <param name="entity">Entity to get the percentage for</param>
/// <returns>Percentage of hate as a sint8</returns>
sint8 GetHatePercentage(Entity* entity);
void SendHateList(Client* client);
///<summary>Gets a list of all the entities in the hate list</summary>
vector<Entity*>* GetHateList();
/* Combat related functions */
bool BrainCastSpell(Spell* spell, Spawn* cast_on, bool calculate_run_loc = true);
/// <summary></summary>
/// <param name=""></param>
/// <param name=""></param>
virtual bool ProcessSpell(Entity* target, float distance);
/// <summary></summary>
/// <returns>True if a buff starts casting</returns>
bool CheckBuffs();
/// <summary>Has the NPC make a melee attack</summary>
/// <param name="target">The target to attack</param>
/// <param name="distance">The current distance from the target</param>
void ProcessMelee(Entity* target, float distance);
/* Encounter related functions */
/// <summary>Adds the given entity and its group and raid members to the encounter list</summary>
/// <param name="entity">Entity we are adding to the encounter list</param>
void AddToEncounter(Entity* entity);
/// <summary>Checks to see if the given entity can loot the corpse</summary>
/// <param name="entity">Entity trying to loot</param>
/// <returns>True if the entity can loot</returns>
bool CheckLootAllowed(Entity* entity);
/// <summary>Gets the size of the encounter list</summary>
/// <returns>The size of the list as an int8</returns>
int8 GetEncounterSize();
/// <summary>Clears the encounter list</summary>
void ClearEncounter();
void SendEncounterList(Client* client);
/// <summary>Gets a copy of the encounter list</summary>
/// <returns>A copy of the encounter list as a vector<Entity*>*</returns>
vector<int32>* GetEncounter();
/// <summary>Checks to see if a player is in the encounter</summary>
/// <returns>True if the encounter list contains a player</returns>
bool PlayerInEncounter() { return m_playerInEncounter; }
bool IsPlayerInEncounter(int32 char_id);
bool IsEntityInEncounter(int32 id, bool skip_read_lock = false);
int32 CountPlayerBotInEncounter();
bool AddToEncounter(int32 id);
/* Helper functions*/
/// <summary>Gets the NPC this brain controls</summary>
/// <returns>The NPC this brain controls</returns>
NPC* GetBody() { return m_body; }
/// <summary>Checks to see if the NPC can cast</summary>
/// <returns>True if the NPC can cast</returns>
bool HasRecovered();
/// <summary>Tells the NPC to move closer to the given target</summary>
/// <param name="target">The target to move closer to</param>
void MoveCloser(Spawn* target);
protected:
// m_body = the npc this brain controls
NPC* m_body;
// m_spellRecovery = time stamp for when the npc can cast again
int32 m_spellRecovery;
private:
// MHateList = mutex to lock and unlock the hate list
Mutex MHateList;
// m_hatelist = the list that stores all the hate,
// entity is the entity this npc hates and the int32 is the value for how much we hate the entity
map<int32, sint32> m_hatelist;
// m_lastTick = the last time we ran this brain
int32 m_lastTick;
// m_tick = the amount of time between Think() calls in milliseconds
int16 m_tick;
// m_encounter = list of players (entities) that will get a reward (xp/loot) for killing this npc
vector<int32> m_encounter;
map<int32, int32> m_encounter_playerlist;
// MEncounter = mutex to lock and unlock the encounter list
Mutex MEncounter;
//m_playerInEncounter = true if a player is added to the encounter
bool m_playerInEncounter;
};
// Extension of the default brain for combat pets
class CombatPetBrain : public Brain {
public:
CombatPetBrain(NPC* body);
virtual ~CombatPetBrain();
void Think();
};
class NonCombatPetBrain : public Brain {
public:
NonCombatPetBrain(NPC* body);
virtual ~NonCombatPetBrain();
void Think();
};
class BlankBrain : public Brain {
public:
BlankBrain(NPC* body);
virtual ~BlankBrain();
void Think();
};
class LuaBrain : public Brain {
public:
LuaBrain(NPC* body);
virtual ~LuaBrain();
void Think();
};
class DumbFirePetBrain : public Brain {
public:
DumbFirePetBrain(NPC* body, Entity* target, int32 expire_time);
virtual ~DumbFirePetBrain();
void Think();
void AddHate(Entity* entity, sint32 hate);
private:
int32 m_expireTime;
};
#endif

View File

@ -0,0 +1,99 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#include "World.h"
#include "Object.h"
#include "Spells.h"
extern World world;
extern ConfigReader configReader;
extern MasterSpellList master_spell_list;
Object::Object(){
clickable = false;
zone_name = 0;
packet_num = 0;
appearance.activity_status = 64;
appearance.pos.state = 1;
appearance.difficulty = 0;
spawn_type = 2;
m_deviceID = 0;
}
Object::~Object(){
}
EQ2Packet* Object::serialize(Player* player, int16 version){
return spawn_serialize(player, version);
}
void Object::HandleUse(Client* client, string command){
vector<TransportDestination*> destinations;
if(GetTransporterID() > 0)
GetZone()->GetTransporters(&destinations, client, GetTransporterID());
if (destinations.size())
{
client->SetTemporaryTransportID(0);
client->ProcessTeleport(this, &destinations, GetTransporterID());
}
else if (client && command.length() > 0 && appearance.show_command_icon == 1 && MeetsSpawnAccessRequirements(client->GetPlayer())){
EntityCommand* entity_command = FindEntityCommand(command);
if (entity_command)
client->GetCurrentZone()->ProcessEntityCommand(entity_command, client->GetPlayer(), client->GetPlayer()->GetTarget());
}
}
Object* Object::Copy(){
Object* new_spawn = new Object();
new_spawn->SetCollector(IsCollector());
new_spawn->SetMerchantID(merchant_id);
new_spawn->SetMerchantType(merchant_type);
new_spawn->SetMerchantLevelRange(GetMerchantMinLevel(), GetMerchantMaxLevel());
if(GetSizeOffset() > 0){
int8 offset = GetSizeOffset()+1;
sint32 tmp_size = size + (rand()%offset - rand()%offset);
if(tmp_size < 0)
tmp_size = 1;
else if(tmp_size >= 0xFFFF)
tmp_size = 0xFFFF;
new_spawn->size = (int16)tmp_size;
}
else
new_spawn->size = size;
new_spawn->SetPrimaryCommands(&primary_command_list);
new_spawn->SetSecondaryCommands(&secondary_command_list);
new_spawn->database_id = database_id;
new_spawn->primary_command_list_id = primary_command_list_id;
new_spawn->secondary_command_list_id = secondary_command_list_id;
memcpy(&new_spawn->appearance, &appearance, sizeof(AppearanceData));
new_spawn->faction_id = faction_id;
new_spawn->target = 0;
new_spawn->SetTotalHP(GetTotalHP());
new_spawn->SetTotalPower(GetTotalPower());
new_spawn->SetHP(GetHP());
new_spawn->SetPower(GetPower());
SetQuestsRequired(new_spawn);
new_spawn->SetTransporterID(GetTransporterID());
new_spawn->SetDeviceID(GetDeviceID());
new_spawn->SetSoundsDisabled(IsSoundsDisabled());
new_spawn->SetOmittedByDBFlag(IsOmittedByDBFlag());
new_spawn->SetLootTier(GetLootTier());
new_spawn->SetLootDropType(GetLootDropType());
return new_spawn;
}

49
old/WorldServer/Object.h Normal file
View File

@ -0,0 +1,49 @@
/*
EQ2Emulator: Everquest II Server Emulator
Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
This file is part of EQ2Emulator.
EQ2Emulator is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
EQ2Emulator is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with EQ2Emulator. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __EQ2_OBJECT__
#define __EQ2_OBJECT__
#include "Spawn.h"
class Object : public Spawn{
public:
Object();
virtual ~Object();
void SetClickable(bool click){
clickable = click;
}
void SetZone(char* zone){
zone_name = zone;
}
Object* Copy();
bool IsObject(){ return true; }
void HandleUse(Client* client, string command);
bool clickable;
char* zone_name;
EQ2Packet* serialize(Player* player, int16 version);
void SetDeviceID(int8 val) { m_deviceID = val; }
int8 GetDeviceID() { return m_deviceID; }
private:
int8 m_deviceID;
};
#endif

Some files were not shown because too many files have changed in this diff Show More