738 lines
17 KiB
Go
738 lines
17 KiB
Go
package quests
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// RegisterQuest sets the basic quest information
|
|
func (q *Quest) RegisterQuest(name, questType, zone string, level int8, description string) {
|
|
q.Name = name
|
|
q.Type = questType
|
|
q.Zone = zone
|
|
q.Level = level
|
|
q.Description = description
|
|
q.SetSaveNeeded(true)
|
|
}
|
|
|
|
// AddQuestStep adds a step to the quest
|
|
func (q *Quest) AddQuestStep(step *QuestStep) bool {
|
|
q.stepsMutex.Lock()
|
|
defer q.stepsMutex.Unlock()
|
|
|
|
// Check if step ID already exists
|
|
if _, exists := q.QuestStepMap[step.ID]; exists {
|
|
return false
|
|
}
|
|
|
|
// Add to all tracking structures
|
|
q.QuestSteps = append(q.QuestSteps, step)
|
|
q.QuestStepMap[step.ID] = step
|
|
q.QuestStepReverseMap[step] = step.ID
|
|
|
|
// Handle task groups
|
|
taskGroup := step.TaskGroup
|
|
if taskGroup == "" {
|
|
taskGroup = step.Description
|
|
}
|
|
|
|
if taskGroup != "" {
|
|
// Add to task group order if new
|
|
if _, exists := q.getTaskGroupByName(taskGroup); !exists {
|
|
q.TaskGroupOrder[q.TaskGroupNum] = taskGroup
|
|
q.TaskGroupNum++
|
|
}
|
|
|
|
// Add step to task group
|
|
q.TaskGroup[taskGroup] = append(q.TaskGroup[taskGroup], step)
|
|
}
|
|
|
|
q.SetSaveNeeded(true)
|
|
return true
|
|
}
|
|
|
|
// CreateQuestStep creates and adds a new quest step
|
|
func (q *Quest) CreateQuestStep(id int32, stepType int8, description string, ids []int32, quantity int32, taskGroup string, locations []*Location, maxVariation, percentage float32, usableItemID int32) *QuestStep {
|
|
step := NewQuestStep(id, stepType, description, ids, quantity, taskGroup, locations, maxVariation, percentage, usableItemID)
|
|
if q.AddQuestStep(step) {
|
|
return step
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RemoveQuestStep removes a step from the quest
|
|
func (q *Quest) RemoveQuestStep(stepID int32) bool {
|
|
q.stepsMutex.Lock()
|
|
defer q.stepsMutex.Unlock()
|
|
|
|
step, exists := q.QuestStepMap[stepID]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
// Remove from maps
|
|
delete(q.QuestStepMap, stepID)
|
|
delete(q.QuestStepReverseMap, step)
|
|
|
|
// Remove from slice
|
|
for i, questStep := range q.QuestSteps {
|
|
if questStep == step {
|
|
q.QuestSteps = append(q.QuestSteps[:i], q.QuestSteps[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
|
|
// Remove from task groups
|
|
taskGroup := step.TaskGroup
|
|
if taskGroup != "" {
|
|
if steps, exists := q.TaskGroup[taskGroup]; exists {
|
|
for i, taskStep := range steps {
|
|
if taskStep == step {
|
|
q.TaskGroup[taskGroup] = append(steps[:i], steps[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
|
|
// If task group is now empty, remove it
|
|
if len(q.TaskGroup[taskGroup]) == 0 {
|
|
delete(q.TaskGroup, taskGroup)
|
|
// Find and remove from task group order
|
|
for orderNum, groupName := range q.TaskGroupOrder {
|
|
if groupName == taskGroup {
|
|
delete(q.TaskGroupOrder, orderNum)
|
|
q.TaskGroupNum--
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove from actions
|
|
q.completeActionsMutex.Lock()
|
|
delete(q.CompleteActions, stepID)
|
|
q.completeActionsMutex.Unlock()
|
|
|
|
q.progressActionsMutex.Lock()
|
|
delete(q.ProgressActions, stepID)
|
|
q.progressActionsMutex.Unlock()
|
|
|
|
q.failedActionsMutex.Lock()
|
|
delete(q.FailedActions, stepID)
|
|
q.failedActionsMutex.Unlock()
|
|
|
|
q.SetSaveNeeded(true)
|
|
return true
|
|
}
|
|
|
|
// GetQuestStep returns a quest step by ID
|
|
func (q *Quest) GetQuestStep(stepID int32) *QuestStep {
|
|
q.stepsMutex.RLock()
|
|
defer q.stepsMutex.RUnlock()
|
|
return q.QuestStepMap[stepID]
|
|
}
|
|
|
|
// SetStepComplete marks a step as complete
|
|
func (q *Quest) SetStepComplete(stepID int32) bool {
|
|
q.stepsMutex.Lock()
|
|
defer q.stepsMutex.Unlock()
|
|
|
|
step, exists := q.QuestStepMap[stepID]
|
|
if !exists || step.Complete() {
|
|
return false
|
|
}
|
|
|
|
step.SetComplete()
|
|
q.StepUpdates = append(q.StepUpdates, step)
|
|
q.SetSaveNeeded(true)
|
|
return true
|
|
}
|
|
|
|
// AddStepProgress adds progress to a step
|
|
func (q *Quest) AddStepProgress(stepID int32, progress int32) bool {
|
|
q.stepsMutex.Lock()
|
|
defer q.stepsMutex.Unlock()
|
|
|
|
step, exists := q.QuestStepMap[stepID]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
// Check percentage chance for success
|
|
if step.Percentage < MaxPercentage && step.Percentage > 0 {
|
|
if step.Percentage <= rand.Float32()*MaxPercentage {
|
|
q.StepFailures = append(q.StepFailures, step)
|
|
return false
|
|
}
|
|
}
|
|
|
|
actualProgress := step.AddStepProgress(progress)
|
|
if actualProgress > 0 {
|
|
q.StepUpdates = append(q.StepUpdates, step)
|
|
q.SetSaveNeeded(true)
|
|
|
|
// TODO: Call progress action if exists
|
|
q.progressActionsMutex.RLock()
|
|
if action, exists := q.ProgressActions[stepID]; exists && action != "" {
|
|
// TODO: Execute Lua script with action
|
|
_ = action // Placeholder for Lua execution
|
|
}
|
|
q.progressActionsMutex.RUnlock()
|
|
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// GetStepProgress returns the current progress of a step
|
|
func (q *Quest) GetStepProgress(stepID int32) int32 {
|
|
step := q.GetQuestStep(stepID)
|
|
if step != nil {
|
|
return step.GetStepProgress()
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// GetQuestStepCompleted checks if a step is completed
|
|
func (q *Quest) GetQuestStepCompleted(stepID int32) bool {
|
|
step := q.GetQuestStep(stepID)
|
|
return step != nil && step.Complete()
|
|
}
|
|
|
|
// GetQuestStep returns the first incomplete step ID
|
|
func (q *Quest) GetCurrentQuestStep() int16 {
|
|
q.stepsMutex.RLock()
|
|
defer q.stepsMutex.RUnlock()
|
|
|
|
for _, step := range q.QuestSteps {
|
|
if !step.Complete() {
|
|
return int16(step.ID)
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// QuestStepIsActive checks if a step is active (not completed)
|
|
func (q *Quest) QuestStepIsActive(stepID int16) bool {
|
|
step := q.GetQuestStep(int32(stepID))
|
|
return step != nil && !step.Complete()
|
|
}
|
|
|
|
// GetTaskGroupStep returns the current task group step
|
|
func (q *Quest) GetTaskGroupStep() int16 {
|
|
q.stepsMutex.RLock()
|
|
defer q.stepsMutex.RUnlock()
|
|
|
|
ret := int16(len(q.TaskGroupOrder))
|
|
|
|
for orderNum, taskGroupName := range q.TaskGroupOrder {
|
|
if steps, exists := q.TaskGroup[taskGroupName]; exists {
|
|
complete := true
|
|
for _, step := range steps {
|
|
if !step.Complete() {
|
|
complete = false
|
|
break
|
|
}
|
|
}
|
|
if !complete && orderNum < ret {
|
|
ret = orderNum
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret
|
|
}
|
|
|
|
// CheckQuestReferencedSpawns checks if a spawn is referenced by any quest step
|
|
func (q *Quest) CheckQuestReferencedSpawns(spawnID int32) bool {
|
|
q.stepsMutex.RLock()
|
|
defer q.stepsMutex.RUnlock()
|
|
|
|
for _, step := range q.QuestSteps {
|
|
if step.Complete() {
|
|
continue
|
|
}
|
|
|
|
switch step.Type {
|
|
case StepTypeKill, StepTypeNormal:
|
|
if step.CheckStepReferencedID(spawnID) {
|
|
return true
|
|
}
|
|
case StepTypeKillRaceReq:
|
|
// TODO: Implement race requirement checking
|
|
// This would require spawn race information
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// CheckQuestKillUpdate checks and updates kill quest steps
|
|
func (q *Quest) CheckQuestKillUpdate(spawnID int32, update bool) bool {
|
|
q.stepsMutex.Lock()
|
|
defer q.stepsMutex.Unlock()
|
|
|
|
hasUpdate := false
|
|
|
|
for _, step := range q.QuestSteps {
|
|
if step.Complete() {
|
|
continue
|
|
}
|
|
|
|
shouldUpdate := false
|
|
switch step.Type {
|
|
case StepTypeKill:
|
|
shouldUpdate = step.CheckStepReferencedID(spawnID)
|
|
case StepTypeKillRaceReq:
|
|
// TODO: Implement race requirement checking
|
|
// shouldUpdate = step.CheckStepKillRaceReqUpdate(spawn)
|
|
}
|
|
|
|
if shouldUpdate {
|
|
if update {
|
|
// Check percentage chance
|
|
passed := true
|
|
if step.Percentage < MaxPercentage {
|
|
passed = step.Percentage > rand.Float32()*MaxPercentage
|
|
}
|
|
|
|
if passed {
|
|
actualProgress := step.AddStepProgress(1)
|
|
if actualProgress > 0 {
|
|
q.StepUpdates = append(q.StepUpdates, step)
|
|
|
|
// TODO: Call progress action
|
|
q.progressActionsMutex.RLock()
|
|
if action, exists := q.ProgressActions[step.ID]; exists && action != "" {
|
|
// TODO: Execute Lua script
|
|
_ = action
|
|
}
|
|
q.progressActionsMutex.RUnlock()
|
|
|
|
hasUpdate = true
|
|
}
|
|
} else {
|
|
q.StepFailures = append(q.StepFailures, step)
|
|
}
|
|
} else {
|
|
hasUpdate = true
|
|
}
|
|
}
|
|
}
|
|
|
|
if hasUpdate && update {
|
|
q.SetSaveNeeded(true)
|
|
}
|
|
|
|
return hasUpdate
|
|
}
|
|
|
|
// CheckQuestChatUpdate checks and updates chat quest steps
|
|
func (q *Quest) CheckQuestChatUpdate(npcID int32, update bool) bool {
|
|
q.stepsMutex.Lock()
|
|
defer q.stepsMutex.Unlock()
|
|
|
|
hasUpdate := false
|
|
|
|
for _, step := range q.QuestSteps {
|
|
if step.Complete() || step.Type != StepTypeChat {
|
|
continue
|
|
}
|
|
|
|
if step.CheckStepReferencedID(npcID) {
|
|
if update {
|
|
actualProgress := step.AddStepProgress(1)
|
|
if actualProgress > 0 {
|
|
q.StepUpdates = append(q.StepUpdates, step)
|
|
|
|
// TODO: Call progress action
|
|
q.progressActionsMutex.RLock()
|
|
if action, exists := q.ProgressActions[step.ID]; exists && action != "" {
|
|
// TODO: Execute Lua script
|
|
_ = action
|
|
}
|
|
q.progressActionsMutex.RUnlock()
|
|
}
|
|
}
|
|
hasUpdate = true
|
|
}
|
|
}
|
|
|
|
if hasUpdate && update {
|
|
q.SetSaveNeeded(true)
|
|
}
|
|
|
|
return hasUpdate
|
|
}
|
|
|
|
// CheckQuestItemUpdate checks and updates item quest steps
|
|
func (q *Quest) CheckQuestItemUpdate(itemID int32, quantity int8) bool {
|
|
q.stepsMutex.Lock()
|
|
defer q.stepsMutex.Unlock()
|
|
|
|
hasUpdate := false
|
|
|
|
for _, step := range q.QuestSteps {
|
|
if step.Complete() || step.Type != StepTypeObtainItem {
|
|
continue
|
|
}
|
|
|
|
if step.CheckStepReferencedID(itemID) {
|
|
// Check percentage chance
|
|
passed := true
|
|
if step.Percentage < MaxPercentage {
|
|
passed = step.Percentage > rand.Float32()*MaxPercentage
|
|
}
|
|
|
|
if passed {
|
|
actualProgress := step.AddStepProgress(int32(quantity))
|
|
if actualProgress > 0 {
|
|
q.StepUpdates = append(q.StepUpdates, step)
|
|
|
|
// TODO: Call progress action
|
|
q.progressActionsMutex.RLock()
|
|
if action, exists := q.ProgressActions[step.ID]; exists && action != "" {
|
|
// TODO: Execute Lua script
|
|
_ = action
|
|
}
|
|
q.progressActionsMutex.RUnlock()
|
|
}
|
|
hasUpdate = true
|
|
} else {
|
|
q.StepFailures = append(q.StepFailures, step)
|
|
}
|
|
}
|
|
}
|
|
|
|
if hasUpdate {
|
|
q.SetSaveNeeded(true)
|
|
}
|
|
|
|
return hasUpdate
|
|
}
|
|
|
|
// CheckQuestLocationUpdate checks and updates location quest steps
|
|
func (q *Quest) CheckQuestLocationUpdate(charX, charY, charZ float32, zoneID int32) bool {
|
|
q.stepsMutex.Lock()
|
|
defer q.stepsMutex.Unlock()
|
|
|
|
hasUpdate := false
|
|
|
|
for _, step := range q.QuestSteps {
|
|
if step.Complete() || step.Type != StepTypeLocation {
|
|
continue
|
|
}
|
|
|
|
if step.CheckStepLocationUpdate(charX, charY, charZ, zoneID) {
|
|
actualProgress := step.AddStepProgress(1)
|
|
if actualProgress > 0 {
|
|
q.StepUpdates = append(q.StepUpdates, step)
|
|
|
|
// TODO: Call progress action
|
|
q.progressActionsMutex.RLock()
|
|
if action, exists := q.ProgressActions[step.ID]; exists && action != "" {
|
|
// TODO: Execute Lua script
|
|
_ = action
|
|
}
|
|
q.progressActionsMutex.RUnlock()
|
|
}
|
|
hasUpdate = true
|
|
}
|
|
}
|
|
|
|
if hasUpdate {
|
|
q.SetSaveNeeded(true)
|
|
}
|
|
|
|
return hasUpdate
|
|
}
|
|
|
|
// CheckQuestSpellUpdate checks and updates spell quest steps
|
|
func (q *Quest) CheckQuestSpellUpdate(spellID int32) bool {
|
|
q.stepsMutex.Lock()
|
|
defer q.stepsMutex.Unlock()
|
|
|
|
hasUpdate := false
|
|
|
|
for _, step := range q.QuestSteps {
|
|
if step.Complete() || step.Type != StepTypeSpell {
|
|
continue
|
|
}
|
|
|
|
if step.CheckStepReferencedID(spellID) {
|
|
// Check percentage chance
|
|
passed := true
|
|
if step.Percentage < MaxPercentage {
|
|
passed = step.Percentage > rand.Float32()*MaxPercentage
|
|
}
|
|
|
|
if passed {
|
|
actualProgress := step.AddStepProgress(1)
|
|
if actualProgress > 0 {
|
|
q.StepUpdates = append(q.StepUpdates, step)
|
|
|
|
// TODO: Call progress action
|
|
q.progressActionsMutex.RLock()
|
|
if action, exists := q.ProgressActions[step.ID]; exists && action != "" {
|
|
// TODO: Execute Lua script
|
|
_ = action
|
|
}
|
|
q.progressActionsMutex.RUnlock()
|
|
}
|
|
hasUpdate = true
|
|
} else {
|
|
q.StepFailures = append(q.StepFailures, step)
|
|
}
|
|
}
|
|
}
|
|
|
|
if hasUpdate {
|
|
q.SetSaveNeeded(true)
|
|
}
|
|
|
|
return hasUpdate
|
|
}
|
|
|
|
// CheckQuestRefIDUpdate checks and updates reference ID quest steps (craft/harvest)
|
|
func (q *Quest) CheckQuestRefIDUpdate(refID int32, quantity int32) bool {
|
|
q.stepsMutex.Lock()
|
|
defer q.stepsMutex.Unlock()
|
|
|
|
hasUpdate := false
|
|
|
|
for _, step := range q.QuestSteps {
|
|
if step.Complete() {
|
|
continue
|
|
}
|
|
|
|
if step.Type == StepTypeHarvest || step.Type == StepTypeCraft {
|
|
if step.CheckStepReferencedID(refID) {
|
|
// Check percentage chance
|
|
passed := true
|
|
if step.Percentage < MaxPercentage {
|
|
passed = step.Percentage > rand.Float32()*MaxPercentage
|
|
}
|
|
|
|
if passed {
|
|
actualProgress := step.AddStepProgress(quantity)
|
|
if actualProgress > 0 {
|
|
q.StepUpdates = append(q.StepUpdates, step)
|
|
|
|
// TODO: Call progress action
|
|
q.progressActionsMutex.RLock()
|
|
if action, exists := q.ProgressActions[step.ID]; exists && action != "" {
|
|
// TODO: Execute Lua script
|
|
_ = action
|
|
}
|
|
q.progressActionsMutex.RUnlock()
|
|
}
|
|
hasUpdate = true
|
|
} else {
|
|
q.StepFailures = append(q.StepFailures, step)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if hasUpdate {
|
|
q.SetSaveNeeded(true)
|
|
}
|
|
|
|
return hasUpdate
|
|
}
|
|
|
|
// GetCompleted checks if the quest is complete
|
|
func (q *Quest) GetCompleted() bool {
|
|
q.stepsMutex.RLock()
|
|
defer q.stepsMutex.RUnlock()
|
|
|
|
for _, step := range q.QuestSteps {
|
|
if !step.Complete() {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// CheckCategoryYellow checks if the quest category should be displayed in yellow
|
|
func (q *Quest) CheckCategoryYellow() bool {
|
|
category := q.Type
|
|
yellowCategories := []string{
|
|
"Signature", "Heritage", "Hallmark", "Deity", "Miscellaneous",
|
|
"Language", "Lore and Legend", "World Event", "Tradeskill",
|
|
}
|
|
|
|
for _, yellowCat := range yellowCategories {
|
|
if strings.EqualFold(category, yellowCat) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// SetStepTimer sets a timer for a quest step
|
|
func (q *Quest) SetStepTimer(duration int32) {
|
|
if duration == 0 {
|
|
q.Timestamp = 0
|
|
} else {
|
|
q.Timestamp = int32(time.Now().Unix()) + duration
|
|
}
|
|
q.SetSaveNeeded(true)
|
|
}
|
|
|
|
// StepFailed handles when a step fails
|
|
func (q *Quest) StepFailed(stepID int32) {
|
|
q.failedActionsMutex.RLock()
|
|
action, exists := q.FailedActions[stepID]
|
|
q.failedActionsMutex.RUnlock()
|
|
|
|
if exists && action != "" {
|
|
// TODO: Execute Lua script for failed action
|
|
_ = action
|
|
}
|
|
}
|
|
|
|
// Helper methods
|
|
|
|
// getTaskGroupByName finds a task group by name
|
|
func (q *Quest) getTaskGroupByName(name string) ([]*QuestStep, bool) {
|
|
steps, exists := q.TaskGroup[name]
|
|
return steps, exists
|
|
}
|
|
|
|
// SetSaveNeeded sets the save needed flag
|
|
func (q *Quest) SetSaveNeeded(needed bool) {
|
|
q.NeedsSave = needed
|
|
}
|
|
|
|
// SetQuestTemporaryState sets the quest temporary state
|
|
func (q *Quest) SetQuestTemporaryState(tempState bool, customDescription string) {
|
|
if !tempState {
|
|
q.TmpRewardCoins = 0
|
|
q.TmpRewardStatus = 0
|
|
// TODO: Clear temporary reward items
|
|
}
|
|
|
|
q.QuestStateTemporary = tempState
|
|
q.QuestTempDescription = customDescription
|
|
q.SetSaveNeeded(true)
|
|
}
|
|
|
|
// CanShareQuestCriteria checks if the quest meets sharing criteria
|
|
func (q *Quest) CanShareQuestCriteria(hasQuest, hasCompleted bool, currentStep int16) bool {
|
|
shareableFlag := q.QuestShareableFlag
|
|
|
|
// Check if quest can be shared at all
|
|
if shareableFlag == ShareableNone {
|
|
return false
|
|
}
|
|
|
|
// Check completed sharing
|
|
if (shareableFlag&ShareableCompleted) == 0 && hasCompleted {
|
|
return false
|
|
}
|
|
|
|
// Check if can only share when completed
|
|
if shareableFlag == ShareableCompleted && !hasCompleted {
|
|
return false
|
|
}
|
|
|
|
// Check during quest sharing
|
|
if (shareableFlag&ShareableDuring) == 0 && hasQuest && currentStep > 1 {
|
|
return false
|
|
}
|
|
|
|
// Check active quest sharing
|
|
if (shareableFlag&ShareableActive) == 0 && hasQuest {
|
|
return false
|
|
}
|
|
|
|
// Check if has quest for sharing
|
|
if (shareableFlag&ShareableCompleted) == 0 && !hasQuest {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Validation methods
|
|
|
|
// ValidateQuest performs basic quest validation
|
|
func (q *Quest) ValidateQuest() error {
|
|
if q.ID <= 0 {
|
|
return fmt.Errorf("quest ID must be positive")
|
|
}
|
|
|
|
if q.Name == "" {
|
|
return fmt.Errorf("quest name cannot be empty")
|
|
}
|
|
|
|
if len(q.Name) > MaxQuestNameLength {
|
|
return fmt.Errorf("quest name too long (max %d)", MaxQuestNameLength)
|
|
}
|
|
|
|
if len(q.Description) > MaxQuestDescriptionLength {
|
|
return fmt.Errorf("quest description too long (max %d)", MaxQuestDescriptionLength)
|
|
}
|
|
|
|
if q.Level < 1 || q.Level > 100 {
|
|
return fmt.Errorf("quest level must be between 1 and 100")
|
|
}
|
|
|
|
if len(q.QuestSteps) == 0 {
|
|
return fmt.Errorf("quest must have at least one step")
|
|
}
|
|
|
|
// Validate steps
|
|
for _, step := range q.QuestSteps {
|
|
if err := q.validateStep(step); err != nil {
|
|
return fmt.Errorf("step %d validation failed: %w", step.ID, err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validateStep validates a single quest step
|
|
func (q *Quest) validateStep(step *QuestStep) error {
|
|
if step.ID <= 0 {
|
|
return fmt.Errorf("step ID must be positive")
|
|
}
|
|
|
|
if step.Type < StepTypeKill || step.Type > StepTypeKillRaceReq {
|
|
return fmt.Errorf("invalid step type: %d", step.Type)
|
|
}
|
|
|
|
if len(step.Description) > MaxStepDescriptionLength {
|
|
return fmt.Errorf("step description too long (max %d)", MaxStepDescriptionLength)
|
|
}
|
|
|
|
if step.Quantity <= 0 {
|
|
return fmt.Errorf("step quantity must be positive")
|
|
}
|
|
|
|
if step.Percentage < MinPercentage || step.Percentage > MaxPercentage {
|
|
return fmt.Errorf("step percentage must be between %.1f and %.1f", MinPercentage, MaxPercentage)
|
|
}
|
|
|
|
// Type-specific validation
|
|
switch step.Type {
|
|
case StepTypeLocation:
|
|
if len(step.Locations) == 0 {
|
|
return fmt.Errorf("location step must have at least one location")
|
|
}
|
|
if step.MaxVariation < MinLocationVariation || step.MaxVariation > MaxLocationVariation {
|
|
return fmt.Errorf("location max variation must be between %.1f and %.1f", MinLocationVariation, MaxLocationVariation)
|
|
}
|
|
default:
|
|
if len(step.IDs) == 0 {
|
|
return fmt.Errorf("non-location step must have at least one referenced ID")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|