# Adding New Quest Types

This document describes how to add new quest types to Eagle. Quests are tasks that unaffiliated heroes want factions to complete before they'll join.

## Deployment Strategy: Three-PR Approach

When adding new quest types, use a three-PR strategy to ensure clients never see quests they don't understand:

1. **PR 1 - Types Only (Server)**: Add proto definitions, Scala types, converters, and all handling logic (fulfillment, failure, LLM prompts). Do NOT generate the quests yet.

2. **PR 2 - Client Handling**: Add client-side display code (C#/Unity) that can render the new quest type.

3. **PR 3 - Quest Generation (Server)**: Add the actual quest creation logic to `QuestCreationUtils.scala`.

**PR Dependencies:**
```
PR 1 (types)
  ├── PR 2 (client)
  └── PR 3 (generation)
```

PR 2 and PR 3 both depend on PR 1, but are independent of each other. They can be developed in parallel after PR 1 is merged, and merged in either order. The key constraint is that PR 3 should not be deployed before PR 2, so clients understand the quest type before the server starts generating it.

## Files to Modify

### Server-Side (Scala)

#### 1. Proto Definition
**File:** `src/main/protobuf/net/eagle0/eagle/common/unaffiliated_hero_quest.proto`

Add a new message type for your quest and add it to the `QuestDetails` oneof:

```protobuf
message MyNewQuest {
    int32 some_field = 1;
    string another_field = 2;
}

// In the QuestDetails message, add to the oneof:
oneof sealed_value {
    // ... existing quests ...
    MyNewQuest my_new_quest = XX;  // Use next available field number
}
```

#### 2. Scala Case Class
**File:** `src/main/scala/net/eagle0/eagle/model/state/quest/Quest.scala`

Add a case class (or case object for quests with no parameters):

```scala
case class MyNewQuest(
    someField: Int,
    anotherField: String
) extends Quest

// For quests with multi-part completion, extend ComponentQuest:
case class MyComponentQuest(
    override val componentCount: Int,
    override val componentsFulfilled: Int,
    targetValue: Int
) extends ComponentQuest {
  override def withComponentsFulfilled(componentsFulfilled: Int): Quest =
    this.copy(componentsFulfilled = componentsFulfilled)
}
```

#### 3. Proto Converter
**File:** `src/main/scala/net/eagle0/eagle/model/proto_converters/QuestConverter.scala`

Add conversions in both `toProto` and `fromProto` methods:

```scala
// In toProto:
case q: MyNewQuest =>
  QuestProto(details = MyNewQuestProto(q.someField, q.anotherField))

// In fromProto:
case MyNewQuestProto(someField, anotherField, _) =>
  MyNewQuest(someField, anotherField)
```

#### 4. Quest Fulfillment Check
**File:** `src/main/scala/net/eagle0/eagle/library/actions/impl/action/CheckForFulfilledQuestsAction.scala`

Add a case to `didFulfillQuest` that returns `true` when the quest conditions are met:

```scala
case MyNewQuest(someField, anotherField) =>
  // Return true if quest is fulfilled
  someConditionIsMet(province, someField)
```

For `ComponentQuest` subclasses, the base case `case q: ComponentQuest => q.componentsFulfilled >= q.componentCount` handles fulfillment automatically.

#### 5. Quest Failure Check
**File:** `src/main/scala/net/eagle0/eagle/library/actions/impl/action/CheckForFailedQuestsAction.scala`

Add a case to `isQuestFailed` if your quest can fail (e.g., if a required faction is destroyed):

```scala
case MyNewQuest(someField, _) =>
  // Return true if quest can no longer be completed
  !factionWithId(someField).exists(_.isActive)
```

Many quests use the default case that returns `false` (quest never fails automatically).

#### 6. LLM Prompt Generators
**File:** `src/main/scala/net/eagle0/eagle/library/actions/llm_prompt_generators/DivineMessagePromptGenerator.scala`

Add a case to `describeQuest` for when a soothsayer reveals the quest:

```scala
case MyNewQuest(someField, anotherField) =>
  TextGenerationSuccess(
    s"$divinedHeroName wants ${faction.name} to do something with $someField."
  )
```

**File:** `src/main/scala/net/eagle0/eagle/library/actions/llm_prompt_generators/QuestEndedGeneratorUtilities.scala`

Add a case to `pastTenseQuestDescription` for quest fulfilled/failed narratives:

```scala
case MyNewQuest(someField, anotherField) =>
  for {
    unaffiliatedHeroName <- unaffiliatedHeroNameResult
  } yield s"$unaffiliatedHeroName wanted ${faction.name} to do something with $someField."
```

#### 7. Quest Creation (PR 3 only)
**File:** `src/main/scala/net/eagle0/eagle/library/util/quest_creation/QuestCreationUtils.scala`

Add a quest creator function and register it in `availableQuests`:

```scala
private def myNewQuests(
    province: ProvinceT,
    allProvinces: Vector[ProvinceT],
    @unused factions: Vector[FactionT],
    @unused battalions: Vector[BattalionT],
    functionalRandom: FunctionalRandom
): RandomState[Vector[Quest]] = {
  // Return empty if quest shouldn't be available
  if (!someCondition) RandomState(Vector(), functionalRandom)
  else {
    functionalRandom.nextIntInclusive(minValue, maxValue).map { value =>
      Vector(MyNewQuest(value, "something"))
    }
  }
}

// Add to availableQuests list:
def availableQuests(...) = functionalRandom.nextFlatMap(
  Vector[QuestCreator](
    // ... existing creators ...
    myNewQuests
  )
) { ... }
```

#### 8. Optional: Quest Command Selectors (AI auto-completion)
**Directory:** `src/main/scala/net/eagle0/eagle/library/util/command_choice_helpers/quest_command_selectors/`

If you want AI players to automatically work toward completing your quest, create a command chooser.

### Client-Side (C#/Unity)

#### 1. Quest Type Display Name
**File:** `src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Eagle/DisplayNames.cs`

Add a case to `QuestTypeString`:

```csharp
case SealedValueOneofCase.MyNewQuest: return "My New Quest";
```

#### 2. Quest Description String
**File:** `src/main/csharp/net/eagle0/clients/unity/eagle0/Assets/Eagle/Table Rows/UnaffiliatedHeroRowController.cs`

Add a case to `ShortQuestString` for displaying the quest in the UI:

```csharp
case SealedValueOneofCase.MyNewQuest: {
    var details = quest.Details.MyNewQuest;
    return $"Do something with {details.SomeField}";
}
```

For quests involving heroes (where you want dynamic name updates), add a case to `SetQuestText` instead.

### Build Files

After making changes, run:

```bash
bazel run gazelle
```

This updates BUILD.bazel files with any new dependencies.

## Quest Type Categories

### Simple Quests
Quests with fixed completion conditions (e.g., `AllianceQuest`, `SuppressRiotByForceQuest`).

### Component Quests
Quests with multi-part completion that track progress via `componentsFulfilled` / `componentCount` (e.g., `AlmsToProvinceQuest`, `DevelopProvincesQuest`). Extend `ComponentQuest` and implement `withComponentsFulfilled`.

### Action Quests
Quests completed by specific player actions rather than state checks. Return `false` in `didFulfillQuest` and handle completion in the relevant command handler (e.g., `ExecutePrisonerQuest` is fulfilled in prisoner management command).

## Testing

1. Build all modified targets:
   ```bash
   bazel build //src/main/scala/net/eagle0/eagle/model/state/quest:quest
   bazel build //src/main/scala/net/eagle0/eagle/model/proto_converters:quest_converter
   ```

2. Run tests:
   ```bash
   bazel test //src/test/scala/...
   ```

3. Verify proto/Scala parity:
   ```bash
   bazel test //src/test/scala/net/eagle0/eagle/model/action_result/types:action_result_type_parity_test
   ```

## Current Quest Types (30 total)

- **Diplomacy**: AllianceQuest, TruceWithFactionQuest, TruceCountQuest, DefeatFactionQuest
- **Development**: ImproveAgricultureQuest, ImproveEconomyQuest, ImproveInfrastructureQuest, TotalDevelopmentQuest
- **Expansion**: SpecificExpansionQuest, ExpandToProvincesQuest
- **Military**: GrandArmyQuest, UpgradeBattalionQuest, FightBeastsAloneQuest
- **Resources**: WealthQuest, AlmsToProvinceQuest, AlmsAcrossRealmQuest, GiveToHeroesInProvinceQuest, GiveToHeroesAcrossRealmQuest
- **Personnel**: DismissSpecificVassalQuest, RescueImprisonedLeaderQuest
- **Prisoner**: ExecutePrisonerQuest, ExilePrisonerQuest, ReleasePrisonerQuest, ReturnPrisonerQuest
- **Province Orders**: DevelopProvincesQuest, MobilizeProvincesQuest
- **Events**: SuppressRiotByForceQuest
