479 lines
14 KiB
Go
479 lines
14 KiB
Go
package quests
|
|
|
|
import "fmt"
|
|
|
|
// Player interface defines the required player functionality for quest system
|
|
type Player interface {
|
|
// Basic player information
|
|
GetID() int32
|
|
GetName() string
|
|
GetLevel() int8
|
|
GetTSLevel() int8
|
|
GetClass() int8
|
|
GetTSClass() int8
|
|
GetRace() int8
|
|
|
|
// Client interface for packet sending
|
|
GetClient() Client
|
|
|
|
// Quest-related player methods
|
|
GetQuest(questID int32) *Quest
|
|
HasQuest(questID int32) bool
|
|
HasQuestBeenCompleted(questID int32) bool
|
|
AddQuest(quest *Quest) error
|
|
RemoveQuest(questID int32) bool
|
|
|
|
// Position and zone information
|
|
GetX() float32
|
|
GetY() float32
|
|
GetZ() float32
|
|
GetZoneID() int32
|
|
|
|
// Faction information
|
|
GetFactionValue(factionID int32) int32
|
|
|
|
// Experience and rewards
|
|
AddCoins(amount int64)
|
|
AddExp(amount int32)
|
|
AddTSExp(amount int32)
|
|
AddStatusPoints(amount int32)
|
|
|
|
// Inventory management
|
|
GetPlayerItemList() ItemList
|
|
|
|
// Arrow color calculation (difficulty indication)
|
|
GetArrowColor(level int8) int8
|
|
GetTSArrowColor(level int8) int8
|
|
}
|
|
|
|
// Client interface defines client functionality needed for quest system
|
|
type Client interface {
|
|
// Basic client information
|
|
GetVersion() int16
|
|
GetNameCRC() int32
|
|
GetPlayer() Player
|
|
|
|
// Packet sending
|
|
QueuePacket(packet Packet)
|
|
SimpleMessage(color int32, message string)
|
|
|
|
// Quest-specific client methods
|
|
SendQuestJournalUpdate(quest *Quest, forceUpdate bool)
|
|
PopulateQuestRewardItems(items *[]Item, packet PacketStruct, arrayName ...string)
|
|
}
|
|
|
|
// Spawn interface defines spawn functionality needed for quest system
|
|
type Spawn interface {
|
|
// Basic spawn information
|
|
GetID() int32
|
|
GetDatabaseID() int32
|
|
GetName() string
|
|
GetRace() int8
|
|
GetModelType() int16
|
|
|
|
// Position information
|
|
GetX() float32
|
|
GetY() float32
|
|
GetZ() float32
|
|
GetZoneID() int32
|
|
}
|
|
|
|
// Spell interface defines spell functionality needed for quest system
|
|
type Spell interface {
|
|
GetSpellID() int32
|
|
GetName() string
|
|
}
|
|
|
|
// Item interface defines item functionality needed for quest system
|
|
type Item interface {
|
|
GetItemID() int32
|
|
GetName() string
|
|
GetIcon(version int16) int32
|
|
GetDetails() ItemDetails
|
|
GetStackCount() int32
|
|
}
|
|
|
|
// ItemDetails interface defines item detail functionality
|
|
type ItemDetails interface {
|
|
GetItemID() int32
|
|
GetUniqueID() int32
|
|
GetCount() int32
|
|
}
|
|
|
|
// ItemList interface defines item list functionality
|
|
type ItemList interface {
|
|
GetItemFromID(itemID int32, count int32) Item
|
|
}
|
|
|
|
// Packet interface defines packet functionality
|
|
type Packet interface {
|
|
// Packet data methods (placeholder)
|
|
Serialize() []byte
|
|
}
|
|
|
|
// PacketStruct interface defines packet structure functionality
|
|
type PacketStruct interface {
|
|
// Packet building methods
|
|
SetDataByName(name string, value any, index ...int)
|
|
SetArrayLengthByName(name string, length int)
|
|
SetArrayDataByName(name string, value any, index int)
|
|
SetSubArrayLengthByName(name string, length int, index int)
|
|
SetSubArrayDataByName(name string, value any, index1, index2 int)
|
|
SetSubstructArrayDataByName(substruct, name string, value any, index int)
|
|
SetItemArrayDataByName(name string, item Item, player Player, index int, flag ...int)
|
|
Serialize() Packet
|
|
GetVersion() int16
|
|
}
|
|
|
|
// QuestOfferer interface defines NPCs that can offer quests
|
|
type QuestOfferer interface {
|
|
Spawn
|
|
GetOfferedQuests() []int32
|
|
CanOfferQuest(questID int32, player Player) bool
|
|
OfferQuest(questID int32, player Player) error
|
|
}
|
|
|
|
// QuestReceiver interface defines NPCs that can receive completed quests
|
|
type QuestReceiver interface {
|
|
Spawn
|
|
GetAcceptedQuests() []int32
|
|
CanAcceptQuest(questID int32, player Player) bool
|
|
AcceptCompletedQuest(questID int32, player Player) error
|
|
}
|
|
|
|
// QuestValidator interface defines quest validation functionality
|
|
type QuestValidator interface {
|
|
ValidatePrerequisites(quest *Quest, player Player) error
|
|
ValidateQuestCompletion(quest *Quest, player Player) error
|
|
ValidateQuestSharing(quest *Quest, sharer, receiver Player) error
|
|
}
|
|
|
|
// QuestRewardProcessor interface defines quest reward processing
|
|
type QuestRewardProcessor interface {
|
|
ProcessQuestRewards(quest *Quest, player Player) error
|
|
CalculateRewards(quest *Quest, player Player) *QuestRewards
|
|
GiveRewards(rewards *QuestRewards, player Player) error
|
|
}
|
|
|
|
// QuestRewards contains calculated quest rewards
|
|
type QuestRewards struct {
|
|
Coins int64 `json:"coins"`
|
|
Experience int32 `json:"experience"`
|
|
TSExperience int32 `json:"ts_experience"`
|
|
StatusPoints int32 `json:"status_points"`
|
|
Items []Item `json:"items"`
|
|
FactionRewards map[int32]int32 `json:"faction_rewards"`
|
|
}
|
|
|
|
// QuestEventHandler interface defines quest event handling
|
|
type QuestEventHandler interface {
|
|
OnQuestStarted(quest *Quest, player Player)
|
|
OnQuestStepCompleted(quest *Quest, step *QuestStep, player Player)
|
|
OnQuestCompleted(quest *Quest, player Player)
|
|
OnQuestFailed(quest *Quest, step *QuestStep, player Player)
|
|
OnQuestAbandoned(quest *Quest, player Player)
|
|
OnQuestShared(quest *Quest, sharer, receiver Player)
|
|
}
|
|
|
|
// Database interface defines database operations for quest system
|
|
type Database interface {
|
|
// Quest CRUD operations
|
|
LoadQuest(questID int32) (*Quest, error)
|
|
SaveQuest(quest *Quest) error
|
|
DeleteQuest(questID int32) error
|
|
LoadAllQuests() ([]*Quest, error)
|
|
|
|
// Player quest operations
|
|
LoadPlayerQuests(playerID int32) ([]*Quest, error)
|
|
SavePlayerQuest(playerID int32, quest *Quest) error
|
|
DeletePlayerQuest(playerID, questID int32) error
|
|
MarkQuestCompleted(playerID, questID int32) error
|
|
IsQuestCompleted(playerID, questID int32) bool
|
|
|
|
// Quest step operations
|
|
SaveQuestStepProgress(playerID, questID, stepID int32, progress int32) error
|
|
LoadQuestStepProgress(playerID, questID, stepID int32) (int32, error)
|
|
}
|
|
|
|
// Logger interface defines logging functionality for quest system
|
|
type Logger interface {
|
|
LogInfo(message string, args ...any)
|
|
LogError(message string, args ...any)
|
|
LogDebug(message string, args ...any)
|
|
LogWarning(message string, args ...any)
|
|
}
|
|
|
|
// Configuration interface defines quest system configuration
|
|
type Configuration interface {
|
|
GetMaxPlayerQuests() int
|
|
GetQuestSharingEnabled() bool
|
|
GetQuestFailureEnabled() bool
|
|
GetQuestTimersEnabled() bool
|
|
GetXPBonusMultiplier() float64
|
|
GetCoinBonusMultiplier() float64
|
|
GetStatusBonusMultiplier() float64
|
|
}
|
|
|
|
// QuestSystemAdapter provides integration with other game systems
|
|
type QuestSystemAdapter struct {
|
|
questManager *QuestManager
|
|
validator QuestValidator
|
|
rewardProcessor QuestRewardProcessor
|
|
eventHandler QuestEventHandler
|
|
database Database
|
|
logger Logger
|
|
config Configuration
|
|
}
|
|
|
|
// NewQuestSystemAdapter creates a new quest system adapter
|
|
func NewQuestSystemAdapter(questManager *QuestManager, validator QuestValidator, rewardProcessor QuestRewardProcessor, eventHandler QuestEventHandler, database Database, logger Logger, config Configuration) *QuestSystemAdapter {
|
|
return &QuestSystemAdapter{
|
|
questManager: questManager,
|
|
validator: validator,
|
|
rewardProcessor: rewardProcessor,
|
|
eventHandler: eventHandler,
|
|
database: database,
|
|
logger: logger,
|
|
config: config,
|
|
}
|
|
}
|
|
|
|
// StartQuest handles starting a new quest for a player
|
|
func (qsa *QuestSystemAdapter) StartQuest(questID int32, player Player) error {
|
|
// Get quest from master list
|
|
quest := qsa.questManager.GetMasterList().GetQuest(questID, true)
|
|
if quest == nil {
|
|
return fmt.Errorf("quest %d not found", questID)
|
|
}
|
|
|
|
// Validate prerequisites
|
|
if qsa.validator != nil {
|
|
if err := qsa.validator.ValidatePrerequisites(quest, player); err != nil {
|
|
return fmt.Errorf("quest prerequisites not met: %w", err)
|
|
}
|
|
}
|
|
|
|
// Check if player can accept more quests
|
|
if qsa.config != nil {
|
|
maxQuests := qsa.config.GetMaxPlayerQuests()
|
|
if maxQuests > 0 && qsa.questManager.GetPlayerQuestCount(player.GetID()) >= maxQuests {
|
|
return fmt.Errorf("player has too many active quests")
|
|
}
|
|
}
|
|
|
|
// Give quest to player
|
|
_, err := qsa.questManager.GiveQuestToPlayer(player.GetID(), questID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Save to database
|
|
if qsa.database != nil {
|
|
if err := qsa.database.SavePlayerQuest(player.GetID(), quest); err != nil {
|
|
qsa.logger.LogError("Failed to save player quest to database: %v", err)
|
|
}
|
|
}
|
|
|
|
// Trigger event
|
|
if qsa.eventHandler != nil {
|
|
qsa.eventHandler.OnQuestStarted(quest, player)
|
|
}
|
|
|
|
// Log
|
|
if qsa.logger != nil {
|
|
qsa.logger.LogInfo("Player %s (%d) started quest %s (%d)", player.GetName(), player.GetID(), quest.Name, questID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CompleteQuest handles completing a quest for a player
|
|
func (qsa *QuestSystemAdapter) CompleteQuest(questID int32, player Player) error {
|
|
// Get player's quest
|
|
quest := qsa.questManager.GetPlayerQuest(player.GetID(), questID)
|
|
if quest == nil {
|
|
return fmt.Errorf("player does not have quest %d", questID)
|
|
}
|
|
|
|
// Validate completion
|
|
if qsa.validator != nil {
|
|
if err := qsa.validator.ValidateQuestCompletion(quest, player); err != nil {
|
|
return fmt.Errorf("quest completion validation failed: %w", err)
|
|
}
|
|
}
|
|
|
|
// Check if quest is actually complete
|
|
if !quest.GetCompleted() {
|
|
return fmt.Errorf("quest is not complete")
|
|
}
|
|
|
|
// Process rewards
|
|
if qsa.rewardProcessor != nil {
|
|
if err := qsa.rewardProcessor.ProcessQuestRewards(quest, player); err != nil {
|
|
qsa.logger.LogError("Failed to process quest rewards: %v", err)
|
|
return fmt.Errorf("failed to process quest rewards: %w", err)
|
|
}
|
|
}
|
|
|
|
// Mark as completed in database
|
|
if qsa.database != nil {
|
|
if err := qsa.database.MarkQuestCompleted(player.GetID(), questID); err != nil {
|
|
qsa.logger.LogError("Failed to mark quest as completed in database: %v", err)
|
|
}
|
|
}
|
|
|
|
// Remove from active quests if not repeatable
|
|
if !quest.IsRepeatable() {
|
|
qsa.questManager.RemoveQuestFromPlayer(player.GetID(), questID)
|
|
}
|
|
|
|
// Trigger event
|
|
if qsa.eventHandler != nil {
|
|
qsa.eventHandler.OnQuestCompleted(quest, player)
|
|
}
|
|
|
|
// Log
|
|
if qsa.logger != nil {
|
|
qsa.logger.LogInfo("Player %s (%d) completed quest %s (%d)", player.GetName(), player.GetID(), quest.Name, questID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateQuestProgress handles updating quest step progress
|
|
func (qsa *QuestSystemAdapter) UpdateQuestProgress(questID, stepID, progress int32, player Player) error {
|
|
// Get player's quest
|
|
quest := qsa.questManager.GetPlayerQuest(player.GetID(), questID)
|
|
if quest == nil {
|
|
return fmt.Errorf("player does not have quest %d", questID)
|
|
}
|
|
|
|
// Update progress
|
|
if !quest.AddStepProgress(stepID, progress) {
|
|
return fmt.Errorf("failed to update quest step progress")
|
|
}
|
|
|
|
// Check if step is now complete
|
|
step := quest.GetQuestStep(stepID)
|
|
if step != nil && step.Complete() {
|
|
// Execute complete action if exists
|
|
if quest.HasCompleteAction(stepID) {
|
|
if err := quest.ExecuteCompleteAction(stepID); err != nil {
|
|
qsa.logger.LogError("Failed to execute quest step complete action: %v", err)
|
|
}
|
|
}
|
|
|
|
// Trigger event
|
|
if qsa.eventHandler != nil {
|
|
qsa.eventHandler.OnQuestStepCompleted(quest, step, player)
|
|
}
|
|
|
|
// Log
|
|
if qsa.logger != nil {
|
|
qsa.logger.LogInfo("Player %s (%d) completed step %d of quest %s (%d)", player.GetName(), player.GetID(), stepID, quest.Name, questID)
|
|
}
|
|
}
|
|
|
|
// Save progress to database
|
|
if qsa.database != nil {
|
|
if err := qsa.database.SaveQuestStepProgress(player.GetID(), questID, stepID, step.GetStepProgress()); err != nil {
|
|
qsa.logger.LogError("Failed to save quest step progress to database: %v", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// AbandonQuest handles abandoning a quest
|
|
func (qsa *QuestSystemAdapter) AbandonQuest(questID int32, player Player) error {
|
|
// Get player's quest
|
|
quest := qsa.questManager.GetPlayerQuest(player.GetID(), questID)
|
|
if quest == nil {
|
|
return fmt.Errorf("player does not have quest %d", questID)
|
|
}
|
|
|
|
// Check if quest can be abandoned
|
|
if !quest.CanDeleteQuest {
|
|
return fmt.Errorf("quest cannot be abandoned")
|
|
}
|
|
|
|
// Remove from player
|
|
if !qsa.questManager.RemoveQuestFromPlayer(player.GetID(), questID) {
|
|
return fmt.Errorf("failed to remove quest from player")
|
|
}
|
|
|
|
// Remove from database
|
|
if qsa.database != nil {
|
|
if err := qsa.database.DeletePlayerQuest(player.GetID(), questID); err != nil {
|
|
qsa.logger.LogError("Failed to delete player quest from database: %v", err)
|
|
}
|
|
}
|
|
|
|
// Trigger event
|
|
if qsa.eventHandler != nil {
|
|
qsa.eventHandler.OnQuestAbandoned(quest, player)
|
|
}
|
|
|
|
// Log
|
|
if qsa.logger != nil {
|
|
qsa.logger.LogInfo("Player %s (%d) abandoned quest %s (%d)", player.GetName(), player.GetID(), quest.Name, questID)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ShareQuest handles sharing a quest between players
|
|
func (qsa *QuestSystemAdapter) ShareQuest(questID int32, sharer, receiver Player) error {
|
|
if !qsa.config.GetQuestSharingEnabled() {
|
|
return fmt.Errorf("quest sharing is disabled")
|
|
}
|
|
|
|
// Get sharer's quest
|
|
quest := qsa.questManager.GetPlayerQuest(sharer.GetID(), questID)
|
|
if quest == nil {
|
|
return fmt.Errorf("sharer does not have quest %d", questID)
|
|
}
|
|
|
|
// Validate sharing
|
|
if qsa.validator != nil {
|
|
if err := qsa.validator.ValidateQuestSharing(quest, sharer, receiver); err != nil {
|
|
return fmt.Errorf("quest sharing validation failed: %w", err)
|
|
}
|
|
}
|
|
|
|
// Check if receiver can accept more quests
|
|
if qsa.config != nil {
|
|
maxQuests := qsa.config.GetMaxPlayerQuests()
|
|
if maxQuests > 0 && qsa.questManager.GetPlayerQuestCount(receiver.GetID()) >= maxQuests {
|
|
return fmt.Errorf("receiver has too many active quests")
|
|
}
|
|
}
|
|
|
|
// Give quest to receiver
|
|
_, err := qsa.questManager.GiveQuestToPlayer(receiver.GetID(), questID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to give quest to receiver: %w", err)
|
|
}
|
|
|
|
// Save to database
|
|
if qsa.database != nil {
|
|
receiverQuest := qsa.questManager.GetPlayerQuest(receiver.GetID(), questID)
|
|
if err := qsa.database.SavePlayerQuest(receiver.GetID(), receiverQuest); err != nil {
|
|
qsa.logger.LogError("Failed to save shared quest to database: %v", err)
|
|
}
|
|
}
|
|
|
|
// Trigger event
|
|
if qsa.eventHandler != nil {
|
|
qsa.eventHandler.OnQuestShared(quest, sharer, receiver)
|
|
}
|
|
|
|
// Log
|
|
if qsa.logger != nil {
|
|
qsa.logger.LogInfo("Player %s (%d) shared quest %s (%d) with %s (%d)",
|
|
sharer.GetName(), sharer.GetID(), quest.Name, questID, receiver.GetName(), receiver.GetID())
|
|
}
|
|
|
|
return nil
|
|
}
|