Compare commits

...
8 Commits
Author SHA1 Message Date
admin 1d02d9cc98 add tsv files to lfs 2025-06-08 07:40:44 -07:00
adminandGitHub 405cbf832f oops (#4143) 2025-06-06 21:30:04 -07:00
adminandGitHub d23f024a35 update the library generator (#4142)
* start updating the library generator

* loadedhero to its own package

* tests pass

* the library generator is working
2025-06-06 21:28:28 -07:00
adminandGitHub 8cfa6fc66e HeroGenerator now returns Hero model objects instead of HeroProtos (#4141)
* most of the conversion

* it builds

* fix the tests
2025-06-06 16:37:31 -07:00
adminandGitHub 02070d7b45 missing AllianceQuest (#4140) 2025-06-06 07:25:03 -07:00
adminandGitHub 1fd30e628e look into the order of the textid assignment (#4139) 2025-06-06 07:23:15 -07:00
adminandGitHub d9526bb880 put Random back into the initial dropdown (#4138) 2025-06-06 07:06:58 -07:00
adminandGitHub faa0ff7bb1 update to latest Claude model and use it alongside gpt (#4137) 2025-06-06 06:49:58 -07:00
50 changed files with 474 additions and 12046 deletions
+1
View File
@@ -6,3 +6,4 @@
*.bytes filter=lfs diff=lfs merge=lfs -text
*.psd filter=lfs diff=lfs merge=lfs -text
*.ttf filter=lfs diff=lfs merge=lfs -text
*.tsv filter=lfs diff=lfs merge=lfs -text
@@ -48,9 +48,8 @@ public class AvailableGameItem : MonoBehaviour {
joinButton.interactable = true;
buttonText.text = "JOIN";
List<HeroView> heroOptions = new List<HeroView> {
new() { Name = "Random", Id = 0, Profession = Profession.NoProfession }
};
List<HeroView> heroOptions =
new List<HeroView> { new() { Id = 0, Profession = Profession.NoProfession } };
for (int i = 0; i < leaders.Count; i++) {
var leader = leaders[i];
heroOptions.Add(new HeroView {
@@ -60,6 +59,7 @@ public class AvailableGameItem : MonoBehaviour {
});
}
heroDropdownController.FallbackText = "Random";
heroDropdownController.AvailableHeroes = heroOptions;
}
}
@@ -60,9 +60,8 @@ public class CreateGameItem : MonoBehaviour {
buttonText.text = "CREATE";
List<HeroView> heroOptions = new List<HeroView> {
new HeroView { Name = "Random", Id = 0, Profession = Profession.NoProfession }
};
List<HeroView> heroOptions =
new List<HeroView> { new() { Id = 0, Profession = Profession.NoProfession } };
for (int i = 0; i < leaders.Count; i++) {
var leader = leaders[i];
heroOptions.Add(new HeroView {
@@ -73,6 +72,7 @@ public class CreateGameItem : MonoBehaviour {
});
}
heroDropdownController.FallbackText = "Random";
heroDropdownController.AvailableHeroes = heroOptions;
heroDropdownController.HeadshotImage.gameObject.SetActive(false);
@@ -61,8 +61,8 @@ namespace eagle {
}
public void AddListener(IClientTextListener listener) {
_listeners.Add(listener);
if (listener.TextId() is {} textId && !String.IsNullOrEmpty(textId)) {
_listeners.Add(listener);
var entry = GetTextEntry(textId);
if (entry != null) { listener.OnTextUpdate(entry.Text, entry.Completed); }
}
@@ -39,12 +39,7 @@ namespace eagle {
? $" ({GUIUtils.ConditionString(ConditionAccessor(hero), ConditionName)})"
: "",
i,
(index, text) => {
if (index < Dropdown.options.Count) {
options[index].text = text;
AssignOptions();
}
});
SetGeneratedText);
listeners.Add(listener);
ClientTextProvider.Provider.AddListener(listener);
@@ -54,6 +49,14 @@ namespace eagle {
}
}
public string FallbackText = "";
private void SetGeneratedText(int index, string text) {
if (index >= Dropdown.options.Count) return;
options[index].text = text;
AssignOptions();
}
private List<TMP_Dropdown.OptionData> options = new();
public void AssignOptions() {
Dropdown.ClearOptions();
@@ -132,7 +135,7 @@ namespace eagle {
}
private TMP_Dropdown.OptionData HeroOption(HeroView hv) {
var texture = Textures.Profession(hv.Profession);
var texture = !Textures ? null : Textures.Profession(hv.Profession);
var op = new TMP_Dropdown.OptionData {
image = !texture ? null
@@ -140,7 +143,7 @@ namespace eagle {
texture,
new Rect(0, 0, texture.width, texture.height),
new Vector2(0.5f, 0.5f)),
text = ""
text = String.IsNullOrEmpty(hv.NameTextId) ? FallbackText : ""
};
return op;
@@ -260,6 +260,7 @@ namespace eagle {
switch (quest.Details.SealedValueCase) {
case SealedValueOneofCase.DefeatFactionQuest: return "Attack Faction";
case SealedValueOneofCase.DismissSpecificVassalQuest: return "Dismiss Vassal";
case SealedValueOneofCase.AllianceQuest: return "Secure Alliance";
case SealedValueOneofCase.AlmsToProvinceQuest: return "Give Alms in Province";
case SealedValueOneofCase.AlmsAcrossRealmQuest: return "Give Alms Across Realm";
case SealedValueOneofCase.GiveToHeroesInProvinceQuest:
@@ -15,10 +15,14 @@ namespace eagle {
_currentCompleted = false;
var scrollRect = GetComponentInParent<ScrollRect>(includeInactive: true);
if (scrollRect != null) { scrollRect.normalizedPosition = new Vector2(0, 1); }
if (scrollRect) { scrollRect.normalizedPosition = new Vector2(0, 1); }
}
OnTextUpdate(ClientTextProvider.Provider.GetTextEntry(TextId));
if (!String.IsNullOrEmpty(_textId)) {
// If the text ID is set, we want to update the view immediately
// to reflect any existing text.
OnTextUpdate(ClientTextProvider.Provider.GetTextEntry(TextId));
}
}
}
Binary file not shown.
1 singularName version https://git-lfs.github.com/spec/v1 pluralName likelihood maxCountMultiplier relativePower economyDevastation agricultureDevastation infrastructureDevastation averageGoldPer averageFoodPer
3 size 2344
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.
Can't render this file because it contains an unexpected character in line 5 and column 5.
Binary file not shown.
1 id version https://git-lfs.github.com/spec/v1 name neighbors neighborPositions hexMapName neighborNames castleCount
3 size 3135
Binary file not shown.
1 actionVigorCost version https://git-lfs.github.com/spec/v1 15 Int
3 size 9623
Binary file not shown.
1 version https://git-lfs.github.com/spec/v1
2 oid sha256:22e059ffe373d2d493c207f7a16450880d9a3e3abc9281f647d630df5c01ebc6
3 size 7563200
Binary file not shown.
1 TypeName version https://git-lfs.github.com/spec/v1 Light Infantry Heavy Infantry Light Cavalry Heavy Cavalry Longbowmen Undead
3 size 6244
Binary file not shown.
1 lightningWisdomFactor version https://git-lfs.github.com/spec/v1 1 double
3 size 6880
@@ -15,7 +15,7 @@ import java.time.format.DateTimeFormatter
import java.util.function.Consumer
object ClaudeServiceImpl {
val model: String = "claude-3-5-sonnet-20240620"
val model: String = "claude-sonnet-4-20250514"
val maxTokens = 2048
private val apiKey = ApiKeys.anthropic
@@ -43,6 +43,7 @@ object ClaudeServiceImpl {
.timeout(Duration.ofSeconds(timeoutSeconds))
.header("X-API-Key", s"${ClaudeServiceImpl.apiKey}")
.header("anthropic-version", "2023-06-01")
.header("Content-Type", "application/json")
}
class ClaudeServiceImpl(
@@ -77,10 +77,6 @@ final class ExternalTextGenerationCaller(
backoffSeconds: Double =
ExternalTextGenerationCaller.initialBackoffSeconds
): Future[HttpResponse[Unit]] = {
if (inputText.contains("Some(")) {
println("SOME!")
}
streamCompletion(
inputText = inputText,
request = serviceImpl.makeRequest(
+1 -1
View File
@@ -74,7 +74,7 @@ scala_library(
srcs = ["package.scala"],
visibility = [
":__subpackages__",
"//src/main/scala/net/eagle0/util:__pkg__",
"//src/main/scala/net/eagle0/util:__subpackages__",
"//src/test:__subpackages__",
],
deps = [
@@ -122,7 +122,7 @@ case class PerformUnaffiliatedHeroesAction(
.withActionResults { gs =>
EndUnaffiliatedHeroActionsPhaseAction(
gameId = gs.gameId,
currentDate = DateConverter.fromProto(gs.currentDate),
currentDate = currentDate,
currentRoundId = gs.currentRoundId,
provinces =
gs.provinces.values.map(ProvinceConverter.fromProto).toVector,
@@ -250,15 +250,15 @@ case class PerformUnaffiliatedHeroesAction(
.getHero(
random = nextFr,
hid = nextHid,
date = gs.currentDate
date = currentDate
)
.map {
case HeroGenerationResponse(
hero,
nameInfo
) =>
innerAcc :+ (toCheck.head, HeroConverter
.fromProto(hero.withId(nextHid)), nameInfo)
innerAcc :+ (toCheck.head, hero
.copy(id = nextHid), nameInfo)
}
)
}
@@ -8,12 +8,9 @@ scala_library(
"//src/test/scala/net/eagle0/eagle:__subpackages__",
],
exports = [
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
],
deps = [
"//src/main/protobuf/net/eagle0/eagle/common:profession_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/scala/net/eagle0/common:functional_random",
"//src/main/scala/net/eagle0/common/name_generation:string_construction_token_generator",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
@@ -22,5 +19,8 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator/hero_generation_response",
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator/name_info",
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator/random_hero_generator",
"//src/main/scala/net/eagle0/eagle/model/state/date",
"//src/main/scala/net/eagle0/eagle/model/state/hero/backstory_version",
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete",
],
)
@@ -1,13 +1,13 @@
package net.eagle0.eagle.library.util.hero_generator
import net.eagle0.common.{FunctionalRandom, RandomState}
import net.eagle0.eagle.common.date.Date
import net.eagle0.eagle.internal.hero.Hero
import net.eagle0.eagle.library.EagleInternalException
import net.eagle0.eagle.library.util.EagleRequire.internalRequire
import net.eagle0.eagle.library.util.hero_generator.hero_generation_response.HeroGenerationResponse
import net.eagle0.eagle.library.util.hero_generator.name_info.NoNameRequest
import net.eagle0.eagle.library.util.hero_generator.random_hero_generator.RandomHeroGenerator
import net.eagle0.eagle.model.state.date.Date
import net.eagle0.eagle.model.state.hero.concrete.HeroC
import net.eagle0.eagle.{HeroId, RoundId}
import scala.util.{Failure, Success}
@@ -16,7 +16,7 @@ object HeroGenerator {
private val randomChance: Double = 0.0
def apply(
pregeneratedHeroes: Vector[Hero],
pregeneratedHeroes: Vector[HeroC],
excludingNames: Vector[String],
functionalRandom: FunctionalRandom
): RandomState[HeroGenerator] =
@@ -33,7 +33,7 @@ object HeroGenerator {
class HeroGenerator(
functionalRandom: FunctionalRandom,
shuffledPregeneratedHeroes: Vector[Hero],
shuffledPregeneratedHeroes: Vector[HeroC],
excludingNames: Vector[String]
) {
private val randomHeroGenerator = new RandomHeroGenerator(
@@ -41,7 +41,7 @@ class HeroGenerator(
excludingNames = excludingNames
)
private var remainingPregeneratedHeroes: Vector[Hero] =
private var remainingPregeneratedHeroes: Vector[HeroC] =
if (
shuffledPregeneratedHeroes == null || shuffledPregeneratedHeroes.isEmpty
) Vector()
@@ -54,7 +54,7 @@ class HeroGenerator(
s"Pregenerated heroes exhausted, no hero with id $hid"
)
val nextHero = remainingPregeneratedHeroes.head
.withId(hid)
.copy(id = hid)
internalRequire(
nextHero.backstoryVersions.nonEmpty,
s"Pregenerated hero ${nextHero.name} has no backstory text"
@@ -74,7 +74,7 @@ class HeroGenerator(
.getHero(hid, date) match {
case Success(HeroGenerationResponse(hero, nameInfo)) =>
HeroGenerationResponse(
hero = hero.clearBackstoryVersions,
hero = hero.copy(backstoryVersions = Vector()),
nameInfo = nameInfo
)
case Failure(_) =>
@@ -7,11 +7,12 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/actions/impl/action:__pkg__",
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator:__subpackages__",
"//src/main/scala/net/eagle0/eagle/service/new_game_creation:__pkg__",
"//src/main/scala/net/eagle0/util:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/util/hero_generator:__subpackages__",
],
deps = [
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator/name_info",
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete",
],
)
@@ -1,9 +1,9 @@
package net.eagle0.eagle.library.util.hero_generator.hero_generation_response
import net.eagle0.eagle.internal.hero.Hero
import net.eagle0.eagle.library.util.hero_generator.name_info.NameInfo
import net.eagle0.eagle.model.state.hero.concrete.HeroC
case class HeroGenerationResponse(
hero: Hero,
hero: HeroC,
nameInfo: NameInfo
)
@@ -8,6 +8,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/actions/name_generation_request:__pkg__",
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator:__subpackages__",
"//src/main/scala/net/eagle0/eagle/service/new_game_creation:__pkg__",
"//src/main/scala/net/eagle0/util:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/util/hero_generator:__subpackages__",
],
deps = [
@@ -12,13 +12,11 @@ scala_library(
"//src/test/scala/net/eagle0/eagle:__subpackages__",
],
exports = [
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete",
],
deps = [
":random_hero_personalities",
"//src/main/protobuf/net/eagle0/eagle/common:profession_scala_proto",
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/scala/net/eagle0/common:functional_random",
"//src/main/scala/net/eagle0/common/name_generation:string_construction_token_generator",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
@@ -26,6 +24,9 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator/hero_generation_response",
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator/name_info",
"//src/main/scala/net/eagle0/eagle/library/util/hero_name_fetcher",
"//src/main/scala/net/eagle0/eagle/model/state/date",
"//src/main/scala/net/eagle0/eagle/model/state/hero/backstory_version",
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete",
],
)
@@ -2,29 +2,24 @@ package net.eagle0.eagle.library.util.hero_generator.random_hero_generator
import net.eagle0.common.{FunctionalRandom, RandomState}
import net.eagle0.eagle.HeroId
import net.eagle0.eagle.common.date.Date
import net.eagle0.eagle.common.gender.Gender
import net.eagle0.eagle.common.gender.Gender.{
GENDER_FEMALE,
GENDER_MALE,
GENDER_OTHER
}
import net.eagle0.eagle.common.hero_backstory_version.BackstoryVersion
import net.eagle0.eagle.common.profession.Profession
import net.eagle0.eagle.common.profession.Profession.{
CHAMPION,
ENGINEER,
MAGE,
NECROMANCER,
NO_PROFESSION,
PALADIN,
RANGER
}
import net.eagle0.eagle.internal.hero.Hero
import net.eagle0.eagle.library.EagleInternalException
import net.eagle0.eagle.library.util.hero_generator.hero_generation_response.HeroGenerationResponse
import net.eagle0.eagle.library.util.hero_generator.name_info.FixedName
import net.eagle0.eagle.library.util.hero_name_fetcher.HeroNameFetcher
import net.eagle0.eagle.model.state.date.Date
import net.eagle0.eagle.model.state.hero.Gender.{Female, Male, Other}
import net.eagle0.eagle.model.state.hero.{Gender, Profession}
import net.eagle0.eagle.model.state.hero.Profession.{
Champion,
Engineer,
Mage,
Necromancer,
NoProfession,
Paladin,
Ranger
}
import net.eagle0.eagle.model.state.hero.backstory_version.BackstoryVersion
import net.eagle0.eagle.model.state.hero.concrete.HeroC
import javax.naming.InsufficientResourcesException
import scala.annotation.tailrec
@@ -40,44 +35,44 @@ object RandomHeroGenerator {
private def genderFromString(genderString: String): Gender =
genderString match {
case "female" => GENDER_FEMALE
case "male" => GENDER_MALE
case "other" => GENDER_OTHER
case "female" => Gender.Female
case "male" => Gender.Male
case "other" => Gender.Other
case _ => throw new EagleInternalException("unknown gender string")
}
private def genderToString(gender: Gender): String =
gender match {
case GENDER_FEMALE => "female"
case GENDER_MALE => "male"
case GENDER_OTHER => "other"
case Gender.Female => "female"
case Gender.Male => "male"
case Gender.Other => "other"
case _ => throw new EagleInternalException("unknown gender")
}
private def withProfessionAndPrimeStat(
bh: Hero,
bh: HeroC,
fr: FunctionalRandom
): RandomState[Hero] =
): RandomState[HeroC] =
primeStat(fr)
.continue { case (ps, nextRandom) =>
nextRandom.nextInt(0, 125).map {
case 1 | 2 => bh.withProfession(MAGE).withWisdom(ps)
case 1 | 2 => bh.withProfession(Mage).copy(wisdom = ps)
case 3 | 4 =>
bh.withProfession(NECROMANCER).withCharisma(ps)
bh.withProfession(Necromancer).copy(charisma = ps)
case 5 | 6 | 7 | 8 | 9 =>
bh.withProfession(ENGINEER).withAgility(ps)
bh.withProfession(Engineer).copy(agility = ps)
case 10 | 11 | 12 | 13 =>
bh.withProfession(PALADIN).withCharisma(ps)
bh.withProfession(Paladin).copy(charisma = ps)
case 14 | 15 | 16 | 17 =>
bh.withProfession(RANGER).withAgility(ps)
bh.withProfession(Ranger).copy(agility = ps)
case 18 | 19 | 20 | 21 =>
bh.withProfession(CHAMPION).withStrength(ps)
case _ => bh.withProfession(NO_PROFESSION)
bh.withProfession(Champion).copy(strength = ps)
case _ => bh.withProfession(NoProfession)
}
}
def withNoProfession(bh: Hero, fr: FunctionalRandom): RandomState[Hero] =
RandomState(bh.withProfession(NO_PROFESSION), fr)
def withNoProfession(bh: HeroC, fr: FunctionalRandom): RandomState[HeroC] =
RandomState(bh.withProfession(NoProfession), fr)
private def primeStat(random: FunctionalRandom): RandomState[Int] =
random
@@ -88,7 +83,7 @@ class RandomHeroGenerator(
functionalRandom: FunctionalRandom,
var excludingNames: Vector[String] = Vector.empty[String],
excludingImagePaths: Vector[String] = Vector.empty[String],
professionAdder: (Hero, FunctionalRandom) => RandomState[Hero] =
professionAdder: (HeroC, FunctionalRandom) => RandomState[HeroC] =
RandomHeroGenerator.withProfessionAndPrimeStat
) {
import RandomHeroGenerator._
@@ -96,7 +91,7 @@ class RandomHeroGenerator(
private def createHeroes(
count: Int,
functionalRandom: FunctionalRandom
): Try[RandomState[Vector[Hero]]] =
): Try[RandomState[Vector[HeroC]]] =
(1 to count)
.foldLeft(RandomState(Vector[String](), functionalRandom)) {
case (RandomState(acc, fr), _) =>
@@ -107,7 +102,7 @@ class RandomHeroGenerator(
.names(genderStrings, excludingNames)
.map(_.zip(genderStrings))
.map {
_.foldLeft(RandomState(Vector[Hero](), fr)) {
_.foldLeft(RandomState(Vector[HeroC](), fr)) {
case (RandomState(heroes, fr), (name, gendString)) =>
excludingNames = excludingNames :+ name
heroWithBaseStats(0, name, genderFromString(gendString), fr)
@@ -120,7 +115,7 @@ class RandomHeroGenerator(
hero.profession,
genderFromString(gendString)
)
.map(hero.withImagePath)
.map(ip => hero.copy(imagePath = ip))
}
.map(heroes :+ _)
}
@@ -164,10 +159,10 @@ class RandomHeroGenerator(
Success(
taken.zip(hids).map { case (hero, hid) =>
val adjustedHero = hero.update(
_.id := hid,
_.nameTextId := s"hn_$hid",
_.backstoryVersions :+= BackstoryVersion(
val adjustedHero = hero.copy(
id = hid,
nameTextId = s"hn_$hid",
backstoryVersions = hero.backstoryVersions :+ BackstoryVersion(
textId = s"hero_${hid}_backstory_0",
date = date
)
@@ -188,13 +183,13 @@ class RandomHeroGenerator(
getClass.getResource("/net/eagle0/eagle/headshots.tsv")
private val professionStringMap = Map(
"no_profession" -> NO_PROFESSION,
"mage" -> MAGE,
"engineer" -> ENGINEER,
"necromancer" -> NECROMANCER,
"paladin" -> PALADIN,
"ranger" -> RANGER,
"champion" -> CHAMPION
"no_profession" -> NoProfession,
"mage" -> Mage,
"engineer" -> Engineer,
"necromancer" -> Necromancer,
"paladin" -> Paladin,
"ranger" -> Ranger,
"champion" -> Champion
)
private def normalStat(random: FunctionalRandom): RandomState[Int] =
@@ -205,10 +200,10 @@ class RandomHeroGenerator(
gender: Gender,
fr: FunctionalRandom
): RandomState[Gender] =
if (gender == GENDER_OTHER)
if (gender == Gender.Other)
fr.nextIntInclusive(0, 1).map {
case 0 => GENDER_FEMALE
case 1 => GENDER_MALE
case 0 => Gender.Female
case 1 => Gender.Male
}
else RandomState(gender, fr)
@@ -245,9 +240,9 @@ class RandomHeroGenerator(
private def gender(random: FunctionalRandom): RandomState[Gender] =
random.nextDouble
.map { d =>
if (d < 0.47) GENDER_FEMALE
else if (d < 0.94) GENDER_MALE
else GENDER_OTHER
if (d < 0.47) Female
else if (d < 0.94) Male
else Other
}
private def heroWithBaseStats(
@@ -255,7 +250,7 @@ class RandomHeroGenerator(
name: String,
gender: Gender,
fr: FunctionalRandom
): RandomState[Hero] =
): RandomState[HeroC] =
fr.nextInt(constitutionMin, 100)
.continue { case (constitution, fr) =>
fr.nextItems(8, normalStat).continue {
@@ -274,7 +269,7 @@ class RandomHeroGenerator(
) =>
RandomHeroPersonalities.randomPersonalityWords(3, innerFr).map {
personalityWords =>
Hero(
HeroC(
id = hid,
name = name,
strength = strength,
@@ -347,13 +342,13 @@ class RandomHeroGenerator(
private val minCacheSize = 10
private val cacheFetchSize = 50
private var cache: RandomState[Vector[Hero]] =
if (functionalRandom == null) RandomState(Vector[Hero](), null)
private var cache: RandomState[Vector[HeroC]] =
if (functionalRandom == null) RandomState(Vector[HeroC](), null)
else createCache(functionalRandom)
private def createCache(
functionalRandom: FunctionalRandom
): RandomState[Vector[Hero]] =
): RandomState[Vector[HeroC]] =
createHeroes(cacheFetchSize, functionalRandom) match {
case Success(rs) => rs
case Failure(e) =>
@@ -9,6 +9,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/util/quest_fulfillment:__pkg__",
"//src/main/scala/net/eagle0/eagle/model:__subpackages__",
"//src/main/scala/net/eagle0/eagle/service/new_game_creation:__pkg__",
"//src/main/scala/net/eagle0/util:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
"//src/test/scala/net/eagle0/eagle/library/util/quest_fulfillment:__pkg__",
"//src/test/scala/net/eagle0/eagle/model/proto_converters:__pkg__",
@@ -35,6 +35,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
"//src/main/scala/net/eagle0/eagle/model/proto_converters:__subpackages__",
"//src/main/scala/net/eagle0/eagle/service/new_game_creation:__pkg__",
"//src/main/scala/net/eagle0/util:__pkg__",
],
exports = [
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
@@ -53,6 +54,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library:__subpackages__",
"//src/main/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
"//src/main/scala/net/eagle0/eagle/model/proto_converters:__subpackages__",
"//src/main/scala/net/eagle0/eagle/service/new_game_creation:__pkg__",
],
exports = [
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete",
@@ -80,6 +82,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/actions/impl:__subpackages__",
"//src/main/scala/net/eagle0/eagle/model/proto_converters:__subpackages__",
"//src/main/scala/net/eagle0/eagle/service/new_game_creation:__pkg__",
"//src/main/scala/net/eagle0/util:__pkg__",
],
exports = [
"//src/main/scala/net/eagle0/eagle/model/state/hero:profession",
@@ -4,14 +4,7 @@ scala_library(
name = "date",
srcs = ["Date.scala"],
visibility = [
"//src/main/scala/net/eagle0/eagle/library:__pkg__",
"//src/main/scala/net/eagle0/eagle/library/actions:__subpackages__",
"//src/main/scala/net/eagle0/eagle/library/util/quest_fulfillment:__pkg__",
"//src/main/scala/net/eagle0/eagle/model:__subpackages__",
"//src/main/scala/net/eagle0/eagle/service/new_game_creation:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
"//src/test/scala/net/eagle0/eagle/library/util/quest_fulfillment:__pkg__",
"//src/test/scala/net/eagle0/eagle/model:__subpackages__",
"//visibility:public",
],
deps = [],
)
@@ -29,6 +29,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library:__subpackages__",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero:__pkg__",
"//src/main/scala/net/eagle0/eagle/service/new_game_creation:__pkg__",
"//src/main/scala/net/eagle0/util/hero_generation:__pkg__",
"//src/test/scala/net/eagle0/eagle/library:__subpackages__",
"//src/test/scala/net/eagle0/eagle/service/new_game_creation:__pkg__",
],
@@ -64,6 +65,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/actions:__subpackages__",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero:__pkg__",
"//src/main/scala/net/eagle0/eagle/service/new_game_creation:__pkg__",
"//src/main/scala/net/eagle0/util/hero_generation:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
"//src/test/scala/net/eagle0/eagle/service/new_game_creation:__pkg__",
],
@@ -5,11 +5,13 @@ scala_library(
srcs = ["BackstoryVersion.scala"],
visibility = [
"//src/main/scala/net/eagle0/eagle/library/actions:__subpackages__",
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator:__subpackages__",
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request:__pkg__",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero:__pkg__",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/llm_request:__pkg__",
"//src/main/scala/net/eagle0/eagle/model/state/hero:__pkg__",
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete:__pkg__",
"//src/main/scala/net/eagle0/eagle/service/new_game_creation:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
],
deps = [
@@ -5,7 +5,10 @@ scala_library(
srcs = ["HeroC.scala"],
visibility = [
"//src/main/scala/net/eagle0/eagle/library/actions:__subpackages__",
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator:__subpackages__",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero:__pkg__",
"//src/main/scala/net/eagle0/eagle/service/new_game_creation:__pkg__",
"//src/test/scala/net/eagle0/eagle/library:__pkg__",
"//src/test/scala/net/eagle0/eagle/library/actions:__subpackages__",
"//src/test/scala/net/eagle0/eagle/library/util:__subpackages__",
],
@@ -31,6 +31,7 @@ import net.eagle0.eagle.library.settings.loaders.{
}
import net.eagle0.eagle.library.util.EagleRequire
import net.eagle0.eagle.library.util.hero_generator.HeroGenerator
import net.eagle0.eagle.model.state.hero.concrete.HeroC
import net.eagle0.eagle.service.controller._
import net.eagle0.eagle.service.new_game_creation.{
ExpandedGameParameters,
@@ -169,7 +170,7 @@ object GamesManager {
gamePersister: Persister,
history: GameHistory,
userToFid: Map[String, FactionId],
pregeneratedHeroes: Vector[Hero]
pregeneratedHeroes: Vector[HeroC]
): GameController = {
val existingNames = (history.last.gameState.killedHeroes ++
history.last.gameState.heroes).values.map(_.name).toVector
@@ -223,7 +224,7 @@ object GamesManager {
gamePersister = persister,
history = gh,
userToFid = runningGame.userToPid,
pregeneratedHeroes = GameParametersUtils.allPregeneratedHeroProtos
pregeneratedHeroes = GameParametersUtils.allPregeneratedHeroes
)
)
)
@@ -105,11 +105,11 @@ class LlmResolver(updateReceiver: LlmUpdateQueuingProxy, gptModelName: String) {
defaultModelName = gptModelName
)
private def newClaudeServiceImpl = new ClaudeServiceImpl(
defaultModelName = "claude-3-5-sonnet-20240620"
defaultModelName = "claude-sonnet-4-20250514"
)
private val chatGptCount = 2
private val claudeCount = 0
private val chatGptCount = 1
private val claudeCount = 1
lazy private val llmCallers = ((1 to claudeCount).map { _ =>
new ExternalTextGenerationCaller(
@@ -15,6 +15,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library:eagle_internal_exception",
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
"//src/main/scala/net/eagle0/eagle/model/state/hero:profession",
"//src/main/scala/net/eagle0/util/hero_generation:loaded_hero",
],
)
@@ -23,17 +24,20 @@ scala_library(
srcs = ["GreatPerson.scala"],
visibility = [
"//src/main/scala/net/eagle0/eagle/service:__pkg__",
"//src/main/scala/net/eagle0/util:__pkg__",
"//src/test/scala/net/eagle0/eagle/service:__pkg__",
"//src/test/scala/net/eagle0/eagle/service/new_game_creation:__pkg__",
],
exports = [
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
"//src/main/scala/net/eagle0/eagle/model/state/hero:profession",
"//src/main/scala/net/eagle0/util/hero_generation:loaded_hero",
],
deps = [
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
"//src/main/scala/net/eagle0/eagle/model/state/hero:profession",
"//src/main/scala/net/eagle0/util/hero_generation:loaded_hero",
],
)
@@ -60,6 +64,8 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library:eagle_internal_exception",
"//src/main/scala/net/eagle0/eagle/library/util:map_generator",
"//src/main/scala/net/eagle0/eagle/library/util:price_index_utils",
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete",
"//src/main/scala/net/eagle0/util/hero_generation:loaded_hero",
"@maven//:com_thesamet_scalapb_scalapb_json4s_2_13",
],
)
@@ -71,7 +77,6 @@ scala_library(
"//src/test/scala/net/eagle0/eagle/service:__subpackages__",
],
deps = [
":great_person",
":start_date",
"//src/main/protobuf/net/eagle0/eagle/internal:hero_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
@@ -79,6 +84,9 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero:gender",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero:profession",
"//src/main/scala/net/eagle0/eagle/model/state/date",
"//src/main/scala/net/eagle0/eagle/model/state/hero/backstory_version",
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete",
"//src/main/scala/net/eagle0/util/hero_generation:loaded_hero",
],
)
@@ -123,6 +131,7 @@ scala_library(
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator/random_hero_generator",
"//src/main/scala/net/eagle0/eagle/model/action_result/generated_text_request",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/date",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/hero",
"//src/main/scala/net/eagle0/eagle/model/state/date",
"//src/main/scala/net/eagle0/eagle/service:persisted_history",
],
@@ -131,6 +140,9 @@ scala_library(
scala_library(
name = "start_date",
srcs = ["StartDate.scala"],
visibility = [
"//src/main/scala/net/eagle0/util:__pkg__",
],
deps = [
"//src/main/scala/net/eagle0/eagle/model/state/date",
],
@@ -146,10 +158,10 @@ scala_library(
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
],
deps = [
":great_person",
":loaded_hero_conversion",
"//src/main/protobuf/net/eagle0/eagle/internal:action_result_scala_proto",
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/library/util:idable",
"//src/main/scala/net/eagle0/util/hero_generation:loaded_hero",
],
)
@@ -3,6 +3,7 @@ package net.eagle0.eagle.service.new_game_creation
import net.eagle0.common.TsvUtils
import net.eagle0.eagle.library.EagleInternalException
import net.eagle0.eagle.model.state.hero.{Gender, Profession}
import net.eagle0.util.hero_generation.LoadedHero
import scala.util.Try
@@ -8,7 +8,9 @@ import net.eagle0.eagle.internal.hero.Hero
import net.eagle0.eagle.internal.province.Province
import net.eagle0.eagle.library.EagleInternalException
import net.eagle0.eagle.library.util.{MapGenerator, PriceIndexUtils}
import net.eagle0.eagle.model.state.hero.concrete.HeroC
import net.eagle0.eagle.{BattalionId, FactionId, HeroId}
import net.eagle0.util.hero_generation.LoadedHero
import scalapb.json4s.JsonFormat
import scala.io.Source
@@ -44,6 +46,11 @@ object GameParametersUtils {
val fixedOthers: Vector[LoadedHero] = fixedHeroes.others
val pregeneratedOthers: Vector[LoadedHero] = pregeneratedHeroes.others
val allPregeneratedHeroes: Vector[HeroC] = (greatPeople.map(
_.greatPerson.hero
) ++ fixedOthers ++ pregeneratedOthers)
.map(LoadedHeroConversion.toHeroC)
val allPregeneratedHeroProtos: Vector[Hero] = (greatPeople.map(
_.greatPerson.hero
) ++ fixedOthers ++ pregeneratedOthers)
@@ -1,37 +1,6 @@
package net.eagle0.eagle.service.new_game_creation
import net.eagle0.eagle.model.state.hero.{Gender, Profession}
import net.eagle0.eagle.{FactionId, HeroId}
import scala.util.hashing.MurmurHash3
case class LoadedHero(
name: String,
factionId: Option[FactionId] = None,
id: HeroId,
backstory: String = "",
strength: Int = 0,
agility: Int = 0,
constitution: Int = 0,
charisma: Int = 0,
wisdom: Int = 0,
integrity: Int = 0,
ambition: Int = 0,
gregariousness: Int = 0,
bravery: Int = 0,
vigor: Int = 0,
profession: Profession = Profession.NoProfession,
imagePath: String = "",
pronounGender: Gender = Gender.Other,
personalityWords: Vector[String] = Vector.empty
) {
def withId(newId: HeroId): LoadedHero = copy(id = newId)
def withFactionId(newFactionId: FactionId): LoadedHero =
copy(factionId = Some(newFactionId))
def nameTextId =
s"fhn_${MurmurHash3.stringHash(name).toHexString}"
def backstoryTextId = s"${name}_backstory 0"
}
import net.eagle0.util.hero_generation.LoadedHero
case class GreatPerson(
hero: LoadedHero,
@@ -1,16 +1,70 @@
package net.eagle0.eagle.service.new_game_creation
import net.eagle0.eagle.common.hero_backstory_version.BackstoryVersion
import net.eagle0.eagle.common.hero_backstory_version.{
BackstoryVersion => BackstoryVersionProto
}
import net.eagle0.eagle.internal.hero.Hero
import net.eagle0.eagle.model.proto_converters.date.DateConverter
import net.eagle0.eagle.model.proto_converters.hero.{
GenderConverter,
ProfessionConverter
}
import net.eagle0.eagle.model.state.hero.backstory_version.BackstoryVersion
import net.eagle0.eagle.model.state.hero.concrete.HeroC
import net.eagle0.eagle.model.state.hero.{Gender, Profession}
import net.eagle0.eagle.{FactionId, HeroId}
import net.eagle0.util.hero_generation.LoadedHero
object LoadedHeroConversion {
def toHeroC(loadedHero: LoadedHero): HeroC = loadedHero match {
case LoadedHero(
name: String,
factionId: Option[FactionId],
id: HeroId,
backstory: String,
strength: Int,
agility: Int,
constitution: Int,
charisma: Int,
wisdom: Int,
integrity: Int,
ambition: Int,
gregariousness: Int,
bravery: Int,
vigor: Int,
profession: Profession,
imagePath: String,
pronounGender: Gender,
personalityWords: Vector[String]
) =>
HeroC(
name = name,
nameTextId = loadedHero.nameTextId,
factionId = factionId,
id = id,
backstoryVersions = Vector(
BackstoryVersion(
textId = loadedHero.backstoryTextId,
date = StartDate.startDate
)
),
strength = strength,
agility = agility,
constitution = constitution,
charisma = charisma,
wisdom = wisdom,
integrity = integrity,
ambition = ambition,
gregariousness = gregariousness,
bravery = bravery,
vigor = vigor,
profession = profession,
imagePath = imagePath,
pronounGender = pronounGender,
personalityWords = personalityWords
)
}
def toHeroProto(loadedHero: LoadedHero): Hero = loadedHero match {
case LoadedHero(
name: String,
@@ -38,7 +92,7 @@ object LoadedHeroConversion {
factionId = factionId,
id = id,
backstoryVersions = Vector(
BackstoryVersion(
BackstoryVersionProto(
textId = loadedHero.backstoryTextId,
date = DateConverter.toProto(StartDate.startDate)
)
@@ -47,9 +47,11 @@ import net.eagle0.eagle.model.action_result.generated_text_request.{
GeneratedTextRequestT
}
import net.eagle0.eagle.model.proto_converters.date.DateConverter
import net.eagle0.eagle.model.proto_converters.hero.HeroConverter
import net.eagle0.eagle.service.PersistedHistory
import net.eagle0.eagle.service.persistence.Persister
import net.eagle0.eagle.{FactionId, GameId, HeroId, ProvinceId}
import net.eagle0.util.hero_generation.LoadedHero
import scala.annotation.tailrec
@@ -188,15 +190,15 @@ object FixedNewGameCreation extends NewGameCreation {
val newHeroes = heroGenerator
.getHeroes(
hids,
DateConverter.toProto(StartDate.startDate)
StartDate.startDate
)
.get
.map { case HeroGenerationResponse(hero, nameInfo) =>
(
hero.update(
_.factionId := fid,
_.loyalty := 75,
_.roundIdJoined := 0
hero.copy(
factionId = Some(fid),
loyalty = 75,
roundIdJoined = Some(0)
),
NameGenerationRequestCreator.nameGenerationRequest(
heroId = hero.id,
@@ -207,7 +209,7 @@ object FixedNewGameCreation extends NewGameCreation {
}
(
ar.withHeroes(newHeroes.map(_._1))
ar.withHeroes(newHeroes.map(_._1).map(HeroConverter.toProto))
.withGeneratedTextRequests(
newHeroes.flatMap(_._2).map(convertNameRequest(_, playerCount))
),
@@ -555,7 +557,7 @@ object FixedNewGameCreation extends NewGameCreation {
.getHero(
random = fr,
hid = heroId,
date = DateConverter.toProto(StartDate.startDate)
date = StartDate.startDate
)
.map { acc :+ _ }
}
@@ -669,9 +671,8 @@ object FixedNewGameCreation extends NewGameCreation {
val fixedHeroesARwithGenerator = withFixedHeroesAR.continue {
case (ar, fr) =>
HeroGenerator(
pregeneratedHeroes = pregeneratedHeroes.map(h =>
LoadedHeroConversion.toHeroProto(loadedHero = h)
),
pregeneratedHeroes =
pregeneratedHeroes.map(LoadedHeroConversion.toHeroC),
excludingNames = withFixedHeroesAR.newValue.newHeroes.map(_.name),
functionalRandom = fr
).map((ar, _))
@@ -695,7 +696,7 @@ object FixedNewGameCreation extends NewGameCreation {
)
}
}.map(newHeroes =>
ar.withHeroes(newHeroes.map(_._1))
ar.withHeroes(newHeroes.map(_._1).map(HeroConverter.toProto))
.withGeneratedTextRequests(
newHeroes.flatMap(_._2).map { convertNameRequest(_, playerCount) }
)
@@ -12,6 +12,7 @@ import net.eagle0.eagle.library.util.IDable.{
mapifyProvinces
}
import net.eagle0.eagle.{BattalionId, FactionId, HeroId, ProvinceId}
import net.eagle0.util.hero_generation.LoadedHero
object StartGameActionResultUtils {
implicit class _StartGameActionResult(value: ActionResult) {
+25 -17
View File
@@ -25,23 +25,31 @@ scala_binary(
],
)
#scala_binary(
# name = "hero_library_generator",
# srcs = ["HeroLibraryGenerator.scala"],
# main_class = "net.eagle0.util.HeroLibraryGenerator",
# resources = [
# "//src/main/resources/net/eagle0/eagle:headshots",
# "//src/main/resources/net/eagle0/eagle:heroes",
# ],
# visibility = ["//visibility:public"],
# deps = [
# "//src/main/scala/net/eagle0/common:functional_random",
# "//src/main/scala/net/eagle0/common/llm_integration:chat_gpt_service_impl",
# "//src/main/scala/net/eagle0/eagle/library/actions/llm_prompt_generators:generator_utilities",
# "//src/main/scala/net/eagle0/eagle/library/util/hero_generator/random_hero_generator",
# "//src/main/scala/net/eagle0/eagle/service/new_game_creation:fixed_heroes",
# ],
#)
scala_binary(
name = "hero_library_generator",
srcs = ["HeroLibraryGenerator.scala"],
main_class = "net.eagle0.util.HeroLibraryGenerator",
resources = [
"//src/main/resources/net/eagle0/eagle:headshots",
"//src/main/resources/net/eagle0/eagle:heroes",
],
visibility = ["//visibility:public"],
deps = [
"//src/main/scala/net/eagle0/common:functional_random",
"//src/main/scala/net/eagle0/common/llm_integration:claude_service_impl",
"//src/main/scala/net/eagle0/common/llm_integration:external_text_generation_caller",
"//src/main/scala/net/eagle0/common/llm_integration:external_text_generation_service_impl",
"//src/main/scala/net/eagle0/common/llm_integration:open_ai_chat_completions_service_impl",
"//src/main/scala/net/eagle0/common/llm_integration:streaming_text_results",
"//src/main/scala/net/eagle0/eagle/library/actions/llm_prompt_generators:generator_utilities",
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator/hero_generation_response",
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator/random_hero_generator",
"//src/main/scala/net/eagle0/eagle/model/proto_converters/date",
"//src/main/scala/net/eagle0/eagle/service/new_game_creation:fixed_heroes",
"//src/main/scala/net/eagle0/eagle/service/new_game_creation:great_person",
"//src/main/scala/net/eagle0/eagle/service/new_game_creation:start_date",
],
)
scala_binary(
name = "name_list_checker",
@@ -1,59 +1,72 @@
package net.eagle0.util
import net.eagle0.common.llm_integration.OpenAIChatCompletionsServiceImpl
import net.eagle0.common.{RandomState, SeededRandom}
import net.eagle0.eagle.common.profession.Profession
import net.eagle0.eagle.internal.hero.Hero
import net.eagle0.eagle.common.gender.Gender
import net.eagle0.common.SeededRandom
import net.eagle0.common.llm_integration.{
ClaudeServiceImpl,
ExternalTextGenerationCaller,
OpenAIChatCompletionsServiceImpl,
StreamingTextResults
}
import net.eagle0.eagle.library.actions.llm_prompt_generators.GeneratorUtilities.{
academyName,
conjunction
}
import net.eagle0.eagle.library.util.hero_generator.hero_generation_response.HeroGenerationResponse
import net.eagle0.eagle.library.util.hero_generator.random_hero_generator.RandomHeroGenerator
import net.eagle0.eagle.service.new_game_creation.FixedHeroes
import net.eagle0.eagle.model.state.hero.concrete.HeroC
import net.eagle0.eagle.model.state.hero.{Gender, Profession}
import net.eagle0.eagle.service.new_game_creation.{FixedHeroes, StartDate}
import net.eagle0.util.hero_generation.LoadedHero
import scala.concurrent.{Await, ExecutionContext, ExecutionContextExecutor}
import java.io.FileOutputStream
import java.util.function.Consumer
import scala.concurrent.duration.Duration
import scala.util.{Random, Success}
import scala.concurrent.{Await, ExecutionContext, ExecutionContextExecutor}
import scala.util.{Random, Success, Using}
object HeroLibraryGenerator {
val heroFilePath = "/net/eagle0/eagle/heroes.tsv"
private val random = new Random()
private val heroFilePath: String = "/net/eagle0/eagle/heroes.tsv"
private val generatedHeroesFilePath = "/net/eagle0/eagle/generated_heroes.tsv"
val heroCount = 10
private val openAICaller = new OpenAIChatCompletionsServiceImpl(
timeoutSeconds = 90,
defaultModelName = OpenAIChatCompletionsServiceImpl.gpt4
)
private val claudeCaller = new ClaudeServiceImpl(
timeoutSeconds = 90,
defaultModelName = ClaudeServiceImpl.model
)
implicit val ec: ExecutionContextExecutor = ExecutionContext.global
val random = new Random()
private def randomElement[T](elts: Seq[T]): T =
elts(random.nextInt(elts.length))
def genderString(gender: Gender): String = gender match {
case Gender.GENDER_UNKNOWN => "unknown"
case Gender.GENDER_FEMALE => "female"
case Gender.GENDER_MALE => "male"
case Gender.GENDER_OTHER => "other"
case Gender.Unrecognized(_) => ???
case Gender.Female => "female"
case Gender.Male => "male"
case Gender.Other => "other"
}
private def professionString(profession: Profession) = profession match {
case Profession.UNKNOWN_PROFESSION => "unknownProfession"
case Profession.MAGE => "mage"
case Profession.NECROMANCER => "necromancer"
case Profession.ENGINEER => "engineer"
case Profession.PALADIN => "paladin"
case Profession.RANGER => "ranger"
case Profession.CHAMPION => "champion"
case Profession.NO_PROFESSION => "noProfession"
case Profession.Unrecognized(_) => ???
case Profession.Mage => "mage"
case Profession.Necromancer => "necromancer"
case Profession.Engineer => "engineer"
case Profession.Paladin => "paladin"
case Profession.Ranger => "ranger"
case Profession.Champion => "champion"
case Profession.NoProfession => "noProfession"
}
private def heroTsvString(hero: Hero): String = {
private def heroTsvString(hero: LoadedHero): String = {
val sum =
hero.strength + hero.agility + hero.constitution + hero.charisma + hero.wisdom
val personalityWords = hero.personalityWords.mkString(", ")
s"${hero.name}\t${hero.strength}\t${hero.agility}\t${hero.constitution}\t${hero.charisma}\t${hero.wisdom}\t$sum\t${hero.integrity}\t${hero.ambition}\t${hero.gregariousness}\t${hero.bravery}\t${hero.vigor.toInt}\t${professionString(
s"${hero.name}\t${hero.strength}\t${hero.agility}\t${hero.constitution}\t${hero.charisma}\t${hero.wisdom}\t$sum\t${hero.integrity}\t${hero.ambition}\t${hero.gregariousness}\t${hero.bravery}\t${hero.vigor}\t${professionString(
hero.profession
)}\tFALSE\t${hero.imagePath}\t${genderString(hero.pronounGender)}\t${hero.backstory}\t$personalityWords"
}
@@ -71,19 +84,19 @@ object HeroLibraryGenerator {
else if (value <= 15) Some(negativeWord)
else None
private def extraPersonalityWords(hero: Hero): Vector[String] = (
private def extraPersonalityWords(hero: HeroC): Vector[String] = (
personalityWord(hero.strength, "strong", "feeble") ++
personalityWord(hero.agility, "agile", "clumsy") ++
personalityWord(hero.charisma, "charismatic", "dull") ++
personalityWord(hero.wisdom, "intelligent", "dumb")
).toVector
private def personalityString(hero: Hero): String =
private def personalityString(hero: HeroC): String =
conjunction(
randomElement(hero.personalityWords) +: extraPersonalityWords(hero)
).get
private def heroLine(hero: Hero): String =
private def heroLine(hero: HeroC): String =
s"(${genderString(hero.pronounGender)}) (${professionString(
hero.profession
)}) (${personalityString(hero)}) ${hero.name}"
@@ -93,12 +106,17 @@ object HeroLibraryGenerator {
val randomHeroes = FixedHeroes.parse(generatedHeroesFilePath).get
val usedHeroes =
heroes.greatPeople ++ heroes.others ++ randomHeroes.greatPeople ++ randomHeroes.others
heroes.greatPeople.map(
_.greatPerson.hero
) ++ heroes.others ++ randomHeroes.greatPeople.map(
_.greatPerson.hero
) ++ randomHeroes.others
val usedNames = usedHeroes.map(_.name)
val usedImagePaths = usedHeroes.map(_.imagePath)
val heroGenerator = new RandomHeroGenerator(
functionalRandom = SeededRandom(seed = new Random().nextLong()),
excludingNames = usedNames,
excludingImagePaths = usedImagePaths
)
@@ -108,57 +126,113 @@ object HeroLibraryGenerator {
)
randomHeroes.others.foreach { hero => println(heroTsvString(hero)) }
val factionHeadNames = conjunction(heroes.greatPeople.map(_.name)).get
val factionHeadNames = conjunction(
heroes.greatPeople.map(_.greatPerson.hero.name)
).get
val factionDescriptions =
s"Possible leaders of the factions are: $factionHeadNames."
val promptString =
s"""This is a fantasy setting with a medieval technology level. Multiple factions are engaged in a civil war for
|control of the kingdom after a mysterious stranger from afar, a powerful mage called The Eagle, invaded the realm.
|King Bregos Fyar's right-hand man, Ikhaan Tarn, betrayed the King to join with The Eagle.
|Several other factions arose under powerful heroes, allied with neither the King nor The Eagle.
|$factionDescriptions
|$academyName, where mages and other practitioners of arcane magics are trained and practice, has remained neutral,
|though some of its members take part in the war.
|
|The following are the names of $heroCount heroes that are not yet affiliated with any faction. Each name
|is preceded by their gender, their profession, if any, and a few notes about their personality or traits,
|all in parentheses. Make up a one-paragraph backstory, no more than 120 words each, for each hero. Make
|the backstory consistent with the background given above. The backstory may refer to the any of the factions
|or their leaders or to other heroes listed here, but it does not need to. Put the backstories in the same
|order as the names given here, but do not number them. Do not include any other context beyond the in-world
|text of the response itself.""".stripMargin
.replaceAll("\n", " ")
val chatGptCaller = new OpenAIChatCompletionsServiceImpl(
timeoutSeconds = 90,
defaultModelName = OpenAIChatCompletionsServiceImpl.gpt4
)
val _ = Using(
new FileOutputStream(
"/tmp/new_generated_heroes" + random.nextLong().toHexString + ".tsv"
)
) { outputStream =>
for (_ <- 1 to 500) {
val newHeroesState =
(1 to heroCount).toVector.foldLeft(Vector[HeroGenerationResponse]()) {
case (acc, hid) =>
val heroState =
heroGenerator
.getHero(hid = 0, date = StartDate.startDate)
.getOrElse(
throw new IllegalStateException(
s"Failed to generate hero with id $hid"
)
)
acc :+ heroState
}
for (_ <- 1 to 50) {
val newHeroesState = (1 to heroCount).toVector.foldLeft(
RandomState[Vector[Hero]](
newValue = Vector(),
SeededRandom(seed = new Random().nextLong())
val newHeroes = newHeroesState.map(_.hero)
// val nameRequests = newHeroesState.map(_.nameInfo)
val caller = new ExternalTextGenerationCaller(
timeoutSeconds = 90,
serviceImpl = claudeCaller
)
) { case (RandomState(acc, nextRandom), _) =>
val heroState = heroGenerator.getHero(random = nextRandom, hid = 0)
heroState.map(h => acc :+ h)
}
val newHeroes = newHeroesState.newValue
var allText = ""
val consumer: Consumer[StreamingTextResults] = {
case StreamingTextResults(streamId, textValue, completed) =>
if (completed) {
val backstories = allText.split("\n").filterNot(_.isEmpty)
if (backstories.length != newHeroes.length) {
println(
s"Expected ${newHeroes.length} backstories, but got ${backstories.length}"
)
} else {
val heroesWithBackstories =
newHeroes.zip(backstories).map { case (hero, backstory) =>
LoadedHero(
name = hero.name,
id = hero.id,
factionId = None,
backstory = backstory,
strength = hero.strength,
agility = hero.agility,
constitution = hero.constitution,
charisma = hero.charisma,
wisdom = hero.wisdom,
integrity = hero.integrity,
ambition = hero.ambition,
gregariousness = hero.gregariousness,
bravery = hero.bravery,
vigor = hero.vigor.toInt,
profession = hero.profession,
imagePath = hero.imagePath,
pronounGender = hero.pronounGender,
personalityWords = hero.personalityWords
)
}
val promptString =
"This is a fantasy setting with a medieval technology level. Multiple factions are engaged in a civil war for " +
"control of the kingdom after a mysterious stranger from afar, a powerful mage called The Eagle, invaded the " +
"realm. King Bregos Fyar's right-hand man, Ikhaan Tarn, betrayed the King to join with The Eagle. Several other " +
"factions arose under powerful heroes, allied with neither the King nor The Eagle. " +
s"Among the great heroes that might be leading factions are $factionHeadNames. " +
s"$academyName, where mages " +
"and other practitioners of arcane magics are trained and practice, has remained neutral, though some of its " +
"members take part in the war.\n\n" +
s"The following are the names of $heroCount heroes that are not yet affiliated with any faction. Each name " +
"is preceded by their gender, their profession, if any, and a few notes about their personality or traits, " +
"all in parentheses. Make up a one-paragraph backstory, no more than 120 words each, for each hero. Make " +
"the backstory consistent with the " +
"background given above. Put the backstories in the same order " +
"as the names given here, but do not number them. Do not include any other context beyond the in-world text " +
"of the response itself.\n\n" +
newHeroes.map(heroLine).mkString("\n")
val future =
chatGptCaller.getCompletion(promptString, 1).map { response =>
val backstories = response.value.split("\n").filterNot(_.isEmpty)
val heroesWithBackstories =
newHeroes.zip(backstories).map { case (hero, backstory) =>
hero.withBackstory(backstory)
}
val tsv =
heroesWithBackstories
.map(heroTsvString)
.mkString("\r\n") + "\r\n"
heroesWithBackstories.map(heroTsvString).mkString("\n")
outputStream.write(tsv.getBytes("UTF-8"))
outputStream.flush()
println(s"Stream completed for $streamId")
}
} else
allText += textValue
}
Await.ready(future, Duration.Inf).value match {
case Some(Success(value)) => println(value)
case _ => throw new IllegalStateException("Failure!")
val promptWithNames =
promptString ++ newHeroes.map(heroLine).mkString("\n")
val future = caller.streamCompletion(promptWithNames, None, consumer, 0)
Await.ready(future, Duration.Inf).value match {
case Some(Success(value)) => println(value)
case _ => throw new IllegalStateException("Failure!")
}
}
}
}
@@ -0,0 +1,18 @@
load("@io_bazel_rules_scala//scala:scala.bzl", "scala_library")
scala_library(
name = "loaded_hero",
srcs = ["LoadedHero.scala"],
visibility = [
"//src/main/scala/net/eagle0:__subpackages__",
"//src/test/scala/net/eagle0/eagle:__subpackages__",
],
exports = [
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
],
deps = [
"//src/main/scala/net/eagle0/eagle:eagle_pkg",
"//src/main/scala/net/eagle0/eagle/model/state/hero:gender",
"//src/main/scala/net/eagle0/eagle/model/state/hero:profession",
],
)
@@ -0,0 +1,34 @@
package net.eagle0.util.hero_generation
import net.eagle0.eagle.{FactionId, HeroId}
import net.eagle0.eagle.model.state.hero.{Gender, Profession}
import scala.util.hashing.MurmurHash3
case class LoadedHero(
name: String,
factionId: Option[FactionId] = None,
id: HeroId,
backstory: String = "",
strength: Int = 0,
agility: Int = 0,
constitution: Int = 0,
charisma: Int = 0,
wisdom: Int = 0,
integrity: Int = 0,
ambition: Int = 0,
gregariousness: Int = 0,
bravery: Int = 0,
vigor: Int = 0,
profession: Profession = Profession.NoProfession,
imagePath: String = "",
pronounGender: Gender = Gender.Other,
personalityWords: Vector[String] = Vector.empty
) {
def withId(newId: HeroId): LoadedHero = copy(id = newId)
def withFactionId(newFactionId: FactionId): LoadedHero =
copy(factionId = Some(newFactionId))
def nameTextId =
s"fhn_${MurmurHash3.stringHash(name).toHexString}"
def backstoryTextId = s"${name}_backstory 0"
}
@@ -12,6 +12,7 @@ scala_test(
"//src/main/scala/net/eagle0/eagle/library:engine_impl",
"//src/main/scala/net/eagle0/eagle/library:game_history",
"//src/main/scala/net/eagle0/eagle/library/util/hero_generator",
"//src/main/scala/net/eagle0/eagle/model/state/hero/concrete",
"//src/main/scala/net/eagle0/eagle/service:in_memory_history",
"@maven//:org_scalamock_scalamock_2_13",
],
@@ -24,6 +24,7 @@ scala_test(
"//src/main/scala/net/eagle0/eagle/service/new_game_creation:loaded_hero_conversion",
"//src/main/scala/net/eagle0/eagle/shardok_interface:battle_resolution",
"//src/main/scala/net/eagle0/eagle/shardok_interface:battle_update_receiver",
"//src/main/scala/net/eagle0/util/hero_generation:loaded_hero",
"//src/test/scala/net/eagle0/common:proto_matchers",
"@maven//:org_scalamock_scalamock_2_13",
],
@@ -15,11 +15,11 @@ import net.eagle0.eagle.service.new_game_creation.{
ExpandedGameParameters,
GreatPerson,
GreatPersonWithBackstory,
LoadedHero,
LoadedHeroConversion,
NewGameCreation
}
import net.eagle0.eagle.service.persistence.Persister
import net.eagle0.util.hero_generation.LoadedHero
import org.scalamock.scalatest.MockFactory
import org.scalatest.BeforeAndAfterEach
import org.scalatest.flatspec.AnyFlatSpec
@@ -1,6 +1,7 @@
package net.eagle0.eagle.service.new_game_creation
import net.eagle0.eagle.model.state.hero.{Gender, Profession}
import net.eagle0.util.hero_generation.LoadedHero
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers