rpg-event-generator 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +261 -6
  2. package/dist/index.js +1482 -54
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -120,35 +120,56 @@ class RPGEventGenerator {
120
120
  * @param {Object} options - Configuration options
121
121
  * @param {number} options.stateSize - Markov chain state size (default: 2)
122
122
  * @param {Array} options.trainingData - Custom training data for Markov chains
123
+ * @param {string} options.theme - Theme for event generation: 'fantasy', 'sci-fi', 'historical'
124
+ * @param {string} options.culture - Cultural context within theme (optional)
123
125
  */
124
126
  constructor(options = {}) {
125
127
  this.chance = new Chance();
126
128
  this.markovGenerator = new SimpleMarkovGenerator({
127
129
  stateSize: options.stateSize || 2
128
130
  });
129
- const defaultTrainingData = options.trainingData || [
130
- // Noble Court Intrigue
131
- 'The royal court is abuzz with whispers of scandal and betrayal', 'A noble lord approaches you with a proposition that could change your destiny', 'Court politics have reached a fever pitch as alliances shift like desert sands', 'The king\'s advisors plot in shadowed corners while the court dances obliviously', 'A mysterious letter arrives sealed with wax from a noble house you don\'t recognize',
132
- // Criminal Underworld
133
- 'The thieves\' guild has put out a contract that bears your name', 'Shadowy figures lurk in alleyways, watching your every move', 'The black market thrives under the cover of night, offering forbidden luxuries', 'A notorious crime lord has taken an interest in your activities', 'Corrupt guards demand tribute while turning a blind eye to greater crimes',
134
- // Supernatural & Mysterious
135
- 'Strange runes appear on your bedroom wall, glowing with ethereal light', 'An ancient prophecy speaks of a hero who matches your description exactly', 'Ghostly apparitions warn of impending doom in fevered dreams', 'A witch in the woods offers you power beyond mortal comprehension', 'Cursed artifacts surface in the market, promising great power at terrible cost',
136
- // Personal & Dramatic
137
- 'Your past sins come back to haunt you in the most unexpected ways', 'A long-lost relative appears with a tale that shakes your world', 'Your reputation draws admirers and enemies in equal measure', 'A betrayal cuts deeper than any blade, leaving scars on your soul', 'Love and ambition war within your heart as opportunities arise',
138
- // Adventure & Exploration
139
- 'Ancient ruins whisper secrets to those brave enough to listen', 'A dragon\'s hoard lies hidden, protected by trials and tribulations', 'Bandits rule the roads, but their leader seems oddly familiar', 'A legendary artifact calls to you from across distant lands', 'The wilderness holds both peril and promise for the bold adventurer',
140
- // Social & Relationship
141
- 'Romantic entanglements complicate your carefully laid plans', 'Old friends become new enemies as loyalties are tested', 'Family secrets emerge that threaten to destroy everything you hold dear', 'Mentors offer wisdom that comes with strings attached', 'Rivals emerge from unexpected places, challenging your hard-won position',
142
- // Economic & Mercantile
143
- 'The market crashes send shockwaves through the merchant class', 'A get-rich-quick scheme promises fortunes but demands your soul', 'Trade wars erupt between rival merchant houses', 'Investment opportunities arise that could make or break your fortune', 'Black market deals offer power but carry the weight of damnation',
144
- // Military & Combat
145
- 'War drums beat as kingdoms prepare for inevitable conflict', 'Desertion offers freedom but brands you a coward forever', 'A duel of honor is proposed, with your reputation on the line', 'Mercenary companies seek captains brave enough to lead them', 'Battle scars tell stories of glory and horror in equal measure',
146
- // Magical & Mystical
147
- 'The veil between worlds thins, allowing magic to seep into reality', 'Curses and blessings intertwine in ways you never expected', 'Ancient bloodlines awaken powers dormant for generations', 'Rituals performed in secret grant power at terrible personal cost', 'The stars themselves seem to conspire in your favor or against you',
148
- // Seasonal & Natural
149
- 'Winter\'s cruel bite forces desperate measures from the populace', 'Spring\'s renewal brings both hope and dangerous new beginnings', 'Summer tournaments test the mettle of warriors and nobles alike', 'Autumn harvests reveal secrets long buried in the fields', 'The changing seasons mirror the turmoil in your own life',
150
- // Dramatic Twists
151
- 'What seems like a blessing reveals itself as a curse in disguise', 'Enemies become allies, and allies become your greatest threat', 'The path of righteousness leads to ruin, while villainy brings reward', 'Fate itself seems to take notice of your actions, for good or ill', 'The world bends around you as your choices reshape reality itself'];
131
+ this.theme = options.theme || 'fantasy';
132
+ this.culture = options.culture;
133
+ this.customTemplates = new Set();
134
+ this.customChains = new Set();
135
+ this.customTrainingData = {};
136
+ this.initializeThemes();
137
+ this.activeChains = new Map();
138
+ this.chainDefinitions = this.initializeChainDefinitions();
139
+ this.difficultySettings = {
140
+ easy: {
141
+ powerRange: [0, 50],
142
+ rewardMultiplier: 1.5,
143
+ penaltyMultiplier: 0.7
144
+ },
145
+ normal: {
146
+ powerRange: [25, 150],
147
+ rewardMultiplier: 1.0,
148
+ penaltyMultiplier: 1.0
149
+ },
150
+ hard: {
151
+ powerRange: [100, 300],
152
+ rewardMultiplier: 0.8,
153
+ penaltyMultiplier: 1.3
154
+ },
155
+ legendary: {
156
+ powerRange: [250, 1000],
157
+ rewardMultiplier: 0.6,
158
+ penaltyMultiplier: 1.6
159
+ }
160
+ };
161
+
162
+ // Initialize time system
163
+ this.timeSystem = {
164
+ currentDay: 1,
165
+ currentSeason: 'spring',
166
+ gameYear: 1,
167
+ timeBasedEvents: new Map()
168
+ };
169
+ const selectedTheme = options.theme || 'fantasy';
170
+ const selectedCulture = options.culture;
171
+ const themeTrainingData = this.getThemeTrainingData(selectedTheme, selectedCulture);
172
+ const defaultTrainingData = options.trainingData || themeTrainingData;
152
173
  try {
153
174
  this.markovGenerator.addData(defaultTrainingData);
154
175
  } catch (error) {
@@ -532,6 +553,370 @@ class RPGEventGenerator {
532
553
  },
533
554
  consequence: 'prudent'
534
555
  }]
556
+ },
557
+ // Event Chain Templates
558
+ BANDIT_AMBUSH: {
559
+ title: 'Bandit Ambush',
560
+ narrative: 'A small group of bandits springs an ambush on your caravan, demanding tribute or blood.',
561
+ choices: [{
562
+ text: 'Fight back fiercely',
563
+ effect: {
564
+ health: [-20, -5],
565
+ reputation: [10, 25]
566
+ },
567
+ consequence: 'hero'
568
+ }, {
569
+ text: 'Pay the tribute',
570
+ effect: {
571
+ gold: [-200, -50],
572
+ reputation: [-10, -5]
573
+ },
574
+ consequence: 'prudent'
575
+ }, {
576
+ text: 'Join the bandits',
577
+ effect: {
578
+ reputation: [-20, -10],
579
+ combat_skill: [5, 10]
580
+ },
581
+ consequence: 'bandit'
582
+ }]
583
+ },
584
+ BANDIT_ARMY: {
585
+ title: 'Bandit Army Assembles',
586
+ narrative: 'The bandit king has gathered a formidable army. Villages are burning, and the threat grows daily.',
587
+ choices: [{
588
+ text: 'Lead a militia to confront them',
589
+ effect: {
590
+ influence: [20, 40],
591
+ health: [-40, -20]
592
+ },
593
+ consequence: 'leader'
594
+ }, {
595
+ text: 'Negotiate with the bandit king',
596
+ effect: {
597
+ gold: [-1000, -500],
598
+ reputation: [-5, 10]
599
+ },
600
+ consequence: 'diplomat'
601
+ }, {
602
+ text: 'Flee to safer lands',
603
+ effect: {
604
+ influence: [-15, -5],
605
+ stress: [-10, -5]
606
+ },
607
+ consequence: 'refugee'
608
+ }]
609
+ },
610
+ ASSASSINATION_PLOT: {
611
+ title: 'Assassination Plot Uncovered',
612
+ narrative: 'Your investigation has revealed a plot to assassinate a high-ranking noble. The conspirators know you\'re onto them.',
613
+ choices: [{
614
+ text: 'Alert the authorities immediately',
615
+ effect: {
616
+ influence: [15, 30],
617
+ reputation: [10, 20]
618
+ },
619
+ consequence: 'hero'
620
+ }, {
621
+ text: 'Confront the conspirators yourself',
622
+ effect: {
623
+ health: [-30, -10],
624
+ reputation: [20, 35]
625
+ },
626
+ consequence: 'investigator'
627
+ }, {
628
+ text: 'Use this information for personal gain',
629
+ effect: {
630
+ gold: [500, 1500],
631
+ reputation: [-25, -10]
632
+ },
633
+ consequence: 'blackmailer'
634
+ }]
635
+ },
636
+ ROYAL_PURGE: {
637
+ title: 'Royal Purge',
638
+ narrative: 'The scandal has triggered a royal purge. Accusations fly, and no one is safe from suspicion.',
639
+ choices: [{
640
+ text: 'Stand your ground and proclaim innocence',
641
+ effect: {
642
+ reputation: [15, 30],
643
+ influence: [10, 25]
644
+ },
645
+ consequence: 'innocent'
646
+ }, {
647
+ text: 'Flee into exile',
648
+ effect: {
649
+ influence: [-50, -20],
650
+ stress: [10, 25]
651
+ },
652
+ consequence: 'exile'
653
+ }, {
654
+ text: 'Testify against others to save yourself',
655
+ effect: {
656
+ reputation: [-30, -15],
657
+ karma: [-20, -10]
658
+ },
659
+ consequence: 'betrayer'
660
+ }]
661
+ },
662
+ TRADE_OPPORTUNITY: {
663
+ title: 'Rare Trade Opportunity',
664
+ narrative: 'A lucrative trade route has opened up, but it requires significant initial investment.',
665
+ choices: [{
666
+ text: 'Invest heavily in the venture',
667
+ effect: {
668
+ gold: [-2000, -1000],
669
+ influence: [10, 25]
670
+ },
671
+ consequence: 'invest'
672
+ }, {
673
+ text: 'Take a calculated risk with moderate investment',
674
+ effect: {
675
+ gold: [-500, -200],
676
+ influence: [5, 15]
677
+ },
678
+ consequence: 'moderate'
679
+ }, {
680
+ text: 'Pass on the opportunity',
681
+ effect: {
682
+ reputation: [-5, 0]
683
+ },
684
+ consequence: 'cautious'
685
+ }]
686
+ },
687
+ FINAL_RECKONING: {
688
+ title: 'Final Reckoning',
689
+ narrative: 'The cursed artifact demands its final price. The entity bound to it appears before you.',
690
+ choices: [{
691
+ text: 'Destroy the artifact',
692
+ effect: {
693
+ health: [-50, -20],
694
+ karma: [30, 50]
695
+ },
696
+ consequence: 'destroyer'
697
+ }, {
698
+ text: 'Bargain with the entity',
699
+ effect: {
700
+ influence: [20, 40],
701
+ health: [-20, -10]
702
+ },
703
+ consequence: 'diplomat'
704
+ }, {
705
+ text: 'Accept the curse as your own',
706
+ effect: {
707
+ power_level: [50, 100],
708
+ health: [-30, -15]
709
+ },
710
+ consequence: 'cursed'
711
+ }]
712
+ },
713
+ // Seasonal Event Templates
714
+ BLOOMING_ROMANCE: {
715
+ title: 'Spring Blossoms',
716
+ narrative: 'The spring air carries the sweet scent of blooming flowers, and romance fills the atmosphere.',
717
+ choices: [{
718
+ text: 'Pursue a romantic interest',
719
+ effect: {
720
+ happiness: [20, 40],
721
+ relationships: [1, 2]
722
+ },
723
+ consequence: 'romantic'
724
+ }, {
725
+ text: 'Focus on personal growth',
726
+ effect: {
727
+ wisdom: [10, 25],
728
+ stress: [-10, -5]
729
+ },
730
+ consequence: 'introspective'
731
+ }, {
732
+ text: 'Throw a garden party',
733
+ effect: {
734
+ influence: [10, 20],
735
+ gold: [-50, -20]
736
+ },
737
+ consequence: 'social'
738
+ }]
739
+ },
740
+ SPRING_FESTIVAL: {
741
+ title: 'Festival of Renewal',
742
+ narrative: 'The village celebrates the spring festival with flowers, music, and joyous gatherings.',
743
+ choices: [{
744
+ text: 'Participate in the celebrations',
745
+ effect: {
746
+ happiness: [15, 30],
747
+ reputation: [5, 15]
748
+ },
749
+ consequence: 'festive'
750
+ }, {
751
+ text: 'Help organize the festival',
752
+ effect: {
753
+ influence: [15, 25],
754
+ gold: [-30, -10]
755
+ },
756
+ consequence: 'organizer'
757
+ }, {
758
+ text: 'Use the crowds for business',
759
+ effect: {
760
+ gold: [20, 50],
761
+ reputation: [-5, 5]
762
+ },
763
+ consequence: 'merchant'
764
+ }]
765
+ },
766
+ SUMMER_TOURNAMENT: {
767
+ title: 'Grand Tournament',
768
+ narrative: 'Warriors from across the land gather for the annual summer tournament, testing their skills in combat.',
769
+ choices: [{
770
+ text: 'Enter the tournament',
771
+ effect: {
772
+ reputation: [20, 40],
773
+ health: [-20, -5]
774
+ },
775
+ consequence: 'competitor'
776
+ }, {
777
+ text: 'Sponsor a contestant',
778
+ effect: {
779
+ influence: [10, 20],
780
+ gold: [-100, -50]
781
+ },
782
+ consequence: 'patron'
783
+ }, {
784
+ text: 'Watch and learn',
785
+ effect: {
786
+ combat_skill: [5, 15],
787
+ reputation: [5, 10]
788
+ },
789
+ consequence: 'spectator'
790
+ }]
791
+ },
792
+ HARVEST_FESTIVAL: {
793
+ title: 'Harvest Celebration',
794
+ narrative: 'The autumn harvest brings bounty and gratitude, celebrated with feasts and thanksgiving.',
795
+ choices: [{
796
+ text: 'Contribute to the harvest',
797
+ effect: {
798
+ karma: [10, 20],
799
+ community_relations: [15, 25]
800
+ },
801
+ consequence: 'contributor'
802
+ }, {
803
+ text: 'Host a harvest feast',
804
+ effect: {
805
+ influence: [20, 30],
806
+ gold: [-80, -40]
807
+ },
808
+ consequence: 'host'
809
+ }, {
810
+ text: 'Trade harvest goods',
811
+ effect: {
812
+ gold: [50, 100],
813
+ influence: [5, 15]
814
+ },
815
+ consequence: 'trader'
816
+ }]
817
+ },
818
+ WINTER_SOLSTICE: {
819
+ title: 'Winter Solstice Ritual',
820
+ narrative: 'The longest night brings reflection and hope as the sun begins its return journey.',
821
+ choices: [{
822
+ text: 'Participate in solstice rituals',
823
+ effect: {
824
+ faith: [10, 25],
825
+ stress: [-15, -5]
826
+ },
827
+ consequence: 'spiritual'
828
+ }, {
829
+ text: 'Host a solstice gathering',
830
+ effect: {
831
+ influence: [15, 25],
832
+ warmth: [20, 40]
833
+ },
834
+ consequence: 'community'
835
+ }, {
836
+ text: 'Contemplate the turning year',
837
+ effect: {
838
+ wisdom: [15, 30],
839
+ insight: [10, 20]
840
+ },
841
+ consequence: 'philosophical'
842
+ }]
843
+ },
844
+ // Time-based Event Templates
845
+ RUMORS_OF_DISSENT: {
846
+ title: 'Whispers of Unrest',
847
+ narrative: 'Rumors of dissatisfaction with the current regime begin circulating through the streets.',
848
+ choices: [{
849
+ text: 'Investigate the rumors',
850
+ effect: {
851
+ influence: [5, 15],
852
+ risk: [10, 20]
853
+ },
854
+ consequence: 'investigator'
855
+ }, {
856
+ text: 'Ignore the gossip',
857
+ effect: {
858
+ stress: [-5, 0]
859
+ },
860
+ consequence: 'apathetic'
861
+ }, {
862
+ text: 'Spread counter-rumors',
863
+ effect: {
864
+ reputation: [-10, -5],
865
+ influence: [5, 10]
866
+ },
867
+ consequence: 'loyalist'
868
+ }]
869
+ },
870
+ MARKET_UNREST: {
871
+ title: 'Market Instability',
872
+ narrative: 'Traders report unusual fluctuations in market prices and growing uncertainty among merchants.',
873
+ choices: [{
874
+ text: 'Invest in stable goods',
875
+ effect: {
876
+ gold: [-200, -100],
877
+ security: [20, 40]
878
+ },
879
+ consequence: 'conservative'
880
+ }, {
881
+ text: 'Speculate on market changes',
882
+ effect: {
883
+ gold: [-100, 300],
884
+ risk: [30, 50]
885
+ },
886
+ consequence: 'speculator'
887
+ }, {
888
+ text: 'Diversify your holdings',
889
+ effect: {
890
+ gold: [-50, -20],
891
+ stability: [15, 25]
892
+ },
893
+ consequence: 'balanced'
894
+ }]
895
+ },
896
+ STRANGE_OCCURRENCES: {
897
+ title: 'Unexplained Phenomena',
898
+ narrative: 'Strange lights in the sky and unexplained events begin occurring with increasing frequency.',
899
+ choices: [{
900
+ text: 'Investigate the phenomena',
901
+ effect: {
902
+ knowledge: [10, 25],
903
+ risk: [10, 20]
904
+ },
905
+ consequence: 'curious'
906
+ }, {
907
+ text: 'Consult local experts',
908
+ effect: {
909
+ gold: [-30, -10],
910
+ insight: [15, 30]
911
+ },
912
+ consequence: 'scholarly'
913
+ }, {
914
+ text: 'Dismiss as superstition',
915
+ effect: {
916
+ stress: [-10, -5]
917
+ },
918
+ consequence: 'skeptical'
919
+ }]
535
920
  }
536
921
  };
537
922
  }
@@ -544,17 +929,19 @@ class RPGEventGenerator {
544
929
  generateEvent(playerContext = {}) {
545
930
  const context = this.analyzeContext(playerContext);
546
931
  const template = this.selectTemplate(context);
932
+ const scaledChoices = this.scaleEffectsForDifficulty(this.generateContextualChoices(template.choices, context), context);
547
933
  return {
548
934
  id: `event_${Date.now()}_${this.chance.guid().substring(0, 8)}`,
549
935
  title: this.generateDynamicTitle(template, context),
550
936
  description: this.generateRichDescription(template, context),
551
937
  narrative: template.narrative,
552
- choices: this.generateContextualChoices(template.choices, context),
938
+ choices: scaledChoices,
553
939
  type: Object.keys(this.templates).find(key => this.templates[key] === template),
554
940
  consequence: null,
555
941
  context: context,
556
942
  urgency: this.calculateUrgency(template, context),
557
- theme: this.determineTheme(template, context)
943
+ theme: this.determineTheme(template, context),
944
+ difficulty: this.calculateDifficultyTier(context.power_level || 0)
558
945
  };
559
946
  }
560
947
 
@@ -573,39 +960,991 @@ class RPGEventGenerator {
573
960
  }
574
961
 
575
962
  /**
576
- * Analyze and enrich player context for sophisticated event generation
963
+ * Initialize thematic training data sets
577
964
  * @private
578
965
  */
579
- analyzeContext(playerContext) {
580
- // Handle null or undefined playerContext
581
- const ctx = playerContext || {};
582
- const context = {
583
- age: ctx.age || 16,
584
- wealth: ctx.gold || 0,
585
- influence: ctx.influence || 0,
586
- reputation: ctx.reputation || 0,
587
- career: ctx.career || null,
588
- skills: ctx.skills || {},
589
- relationships: ctx.relationships || [],
590
- location: ctx.location || this.generateLocation(),
591
- season: ctx.season || 'spring',
592
- health: ctx.health || 100,
593
- stress: ctx.stress || 0,
594
- happiness: ctx.happiness || 50,
595
- karma: ctx.karma || 0,
596
- faith: ctx.faith || 0,
597
- vices: ctx.vices || [],
598
- secrets: ctx.secrets || [],
599
- ambitions: ctx.ambitions || []
966
+ initializeThemes() {
967
+ this.themes = {
968
+ fantasy: [
969
+ // Noble Court Intrigue
970
+ 'The royal court is abuzz with whispers of scandal and betrayal', 'A noble lord approaches you with a proposition that could change your destiny', 'Court politics have reached a fever pitch as alliances shift like desert sands', 'The king\'s advisors plot in shadowed corners while the court dances obliviously', 'A mysterious letter arrives sealed with wax from a noble house you don\'t recognize',
971
+ // Criminal Underworld
972
+ 'The thieves\' guild has put out a contract that bears your name', 'Shadowy figures lurk in alleyways, watching your every move', 'The black market thrives under the cover of night, offering forbidden luxuries', 'A notorious crime lord has taken an interest in your activities', 'Corrupt guards demand tribute while turning a blind eye to greater crimes',
973
+ // Supernatural & Mysterious
974
+ 'Strange runes appear on your bedroom wall, glowing with ethereal light', 'An ancient prophecy speaks of a hero who matches your description exactly', 'Ghostly apparitions warn of impending doom in fevered dreams', 'A witch in the woods offers you power beyond mortal comprehension', 'Cursed artifacts surface in the market, promising great power at terrible cost',
975
+ // Personal & Dramatic
976
+ 'Your past sins come back to haunt you in the most unexpected ways', 'A long-lost relative appears with a tale that shakes your world', 'Your reputation draws admirers and enemies in equal measure', 'A betrayal cuts deeper than any blade, leaving scars on your soul', 'Love and ambition war within your heart as opportunities arise',
977
+ // Adventure & Exploration
978
+ 'Ancient ruins whisper secrets to those brave enough to listen', 'A dragon\'s hoard lies hidden, protected by trials and tribulations', 'Bandits rule the roads, but their leader seems oddly familiar', 'A legendary artifact calls to you from across distant lands', 'The wilderness holds both peril and promise for the bold adventurer',
979
+ // Social & Relationship
980
+ 'Romantic entanglements complicate your carefully laid plans', 'Old friends become new enemies as loyalties are tested', 'Family secrets emerge that threaten to destroy everything you hold dear', 'Mentors offer wisdom that comes with strings attached', 'Rivals emerge from unexpected places, challenging your hard-won position',
981
+ // Economic & Mercantile
982
+ 'The market crashes send shockwaves through the merchant class', 'A get-rich-quick scheme promises fortunes but demands your soul', 'Trade wars erupt between rival merchant houses', 'Investment opportunities arise that could make or break your fortune', 'Black market deals offer power but carry the weight of damnation',
983
+ // Military & Combat
984
+ 'War drums beat as kingdoms prepare for inevitable conflict', 'Desertion offers freedom but brands you a coward forever', 'A duel of honor is proposed, with your reputation on the line', 'Mercenary companies seek captains brave enough to lead them', 'Battle scars tell stories of glory and horror in equal measure',
985
+ // Magical & Mystical
986
+ 'The veil between worlds thins, allowing magic to seep into reality', 'Curses and blessings intertwine in ways you never expected', 'Ancient bloodlines awaken powers dormant for generations', 'Rituals performed in secret grant power at terrible personal cost', 'The stars themselves seem to conspire in your favor or against you',
987
+ // Seasonal & Natural
988
+ 'Winter\'s cruel bite forces desperate measures from the populace', 'Spring\'s renewal brings both hope and dangerous new beginnings', 'Summer tournaments test the mettle of warriors and nobles alike', 'Autumn harvests reveal secrets long buried in the fields', 'The changing seasons mirror the turmoil in your own life',
989
+ // Dramatic Twists
990
+ 'What seems like a blessing reveals itself as a curse in disguise', 'Enemies become allies, and allies become your greatest threat', 'The path of righteousness leads to ruin, while villainy brings reward', 'Fate itself seems to take notice of your actions, for good or ill', 'The world bends around you as your choices reshape reality itself'],
991
+ 'sci-fi': [
992
+ // Corporate Intrigue
993
+ 'Megacorporations wage shadow wars through proxy conflicts and data manipulation', 'Neural implants malfunction, flooding your mind with corporate secrets', 'A black-market AI offers forbidden knowledge at the cost of your sanity', 'Corporate espionage turns deadly when rival agents converge on your location', 'Whistleblower data reveals systemic corruption in the highest levels of power',
994
+ // Cyberpunk Underworld
995
+ 'Hackers breach your personal network, exposing vulnerabilities you never knew existed', 'Street gangs control the undercity with drone swarms and illegal augmentations', 'The dark web pulses with illegal tech trades and forbidden experiments', 'Corporate bounty hunters track you through the neon-lit sprawl', 'Data runners risk everything for the ultimate information score',
996
+ // Space & Exploration
997
+ 'Alien artifacts whisper secrets from beyond the galactic rim', 'Space pirates demand tribute or face the void of decompression', 'Ancient derelict ships hold treasures and horrors from forgotten eras', 'Wormhole anomalies bend reality, creating unpredictable temporal events', 'Colony worlds rebel against Earth\'s iron-fisted governance',
998
+ // AI & Technology
999
+ 'Sentient AIs manipulate events from within the global network', 'Nanobot swarms escape containment, evolving beyond their programming', 'Virtual reality simulations bleed into the real world disastrously', 'Genetic engineering experiments create monstrous hybrid beings', 'Quantum computers predict the future, but at what cost to free will?',
1000
+ // Dystopian Society
1001
+ 'Social credit systems reward compliance and punish deviation', 'Surveillance drones watch every move, every transaction, every thought', 'Rebellions simmer beneath the surface of totalitarian control', 'Resource wars rage over the last viable mining asteroids', 'Climate engineering projects backfire spectacularly',
1002
+ // Personal & Cybernetic
1003
+ 'Implant malfunctions cause vivid hallucinations and system crashes', 'Identity theft becomes literal as someone steals your digital consciousness', 'Memory wipes erase crucial knowledge, leaving you vulnerable', 'Cybernetic enhancements demand maintenance that costs more than you can afford', 'Neural links create unintended telepathic connections with strangers',
1004
+ // Economic & Corporate
1005
+ 'Cryptocurrency markets crash, wiping out fortunes in digital dust', 'Hostile takeovers turn friendly acquisitions into bloody boardroom coups', 'Trade wars erupt between orbital habitats and planetary governments', 'Investment opportunities in black-market tech promise riches or ruin', 'Corporate espionage turns personal when family members are leveraged',
1006
+ // Military & Combat
1007
+ 'Drone wars rage in the skies above abandoned cities', 'Cyber warfare disables entire infrastructure networks', 'Private military companies offer their services to the highest bidder', 'Nuclear deterrence fails when EMP weapons render missiles useless', 'Bioterrorism releases engineered plagues upon unsuspecting populations',
1008
+ // Mystery & Conspiracy
1009
+ 'Government cover-ups hide the truth about alien visitations', 'Time travel experiments create paradoxical timeline fractures', 'Parallel dimensions bleed through, creating impossible phenomena', 'Ancient alien technology reactivates with catastrophic consequences', 'Conspiracy theories prove true, but the reality is far worse',
1010
+ // Environmental & Planetary
1011
+ 'Terraforming projects unleash ancient microbes from the soil', 'Climate control systems fail, creating unpredictable weather patterns', 'Asteroid mining operations awaken dormant extraterrestrial threats', 'Ocean acidification drives aquatic mutations to the surface', 'Atmospheric processors malfunction, creating toxic breathing conditions'],
1012
+ historical: [
1013
+ // Medieval Court Intrigue
1014
+ 'The king\'s advisors plot in shadowed corners of the great hall', 'Noble houses form secret alliances against the crown', 'Whispers of heresy spread through the royal court like wildfire', 'A foreign diplomat offers treacherous propositions under diplomatic immunity', 'Court jesters hide dangerous truths behind masks of folly',
1015
+ // Warfare & Conquest
1016
+ 'Armies mass at the borders, their banners fluttering in the wind of war', 'Siege engines pound ancient walls as defenders hold the line', 'Cavalry charges break enemy formations in clouds of dust and blood', 'Naval blockades starve cities into submission or desperate rebellion', 'Mercenary companies switch sides based on the highest bidder',
1017
+ // Exploration & Discovery
1018
+ 'New World expeditions return with gold and strange diseases', 'Ancient artifacts surface from archaeological digs, rewriting history', 'Trade caravans brave bandits and harsh terrains for exotic goods', 'Mapping expeditions chart unknown territories and hostile natives', 'Scientific discoveries challenge religious doctrines and established truths',
1019
+ // Social & Class Conflict
1020
+ 'Peasant revolts spread like contagion through the countryside', 'Guilds clash over trade monopolies and apprenticeship rights', 'Religious schisms divide communities along ideological lines', 'Women maneuver within restrictive social structures for power and influence', 'Immigrants bring new customs that clash with traditional ways',
1021
+ // Economic & Mercantile
1022
+ 'Market monopolies drive independent merchants to desperation', 'Banking houses manipulate currency values for political gain', 'Trade embargoes create black markets and smuggling operations', 'Agricultural failures lead to famines and social unrest', 'Colonial exploitation enriches the crown while impoverishing subjects',
1023
+ // Political & Diplomatic
1024
+ 'Treaty negotiations mask underlying territorial ambitions', 'Spy networks weave webs of deception across national borders', 'Succession crises threaten to plunge nations into civil war', 'Diplomatic incidents escalate into full-scale military conflicts', 'Royal marriages forge alliances that crumble under stress',
1025
+ // Religious & Ideological
1026
+ 'Inquisitions root out heresy with fire and torture', 'Crusades call warriors to distant lands for faith and glory', 'Reformation movements challenge the authority of established churches', 'Cult leaders promise salvation through devotion and sacrifice', 'Religious relics inspire pilgrimages and bloody conflicts',
1027
+ // Personal & Dramatic
1028
+ 'Family feuds span generations, poisoning bloodlines with vengeance', 'Scandals rock aristocratic families, threatening social standing', 'Personal ambitions clash with familial duties and expectations', 'Love affairs defy social conventions and familial arrangements', 'Betrayals cut deeper than any blade, leaving lasting scars',
1029
+ // Natural & Environmental
1030
+ 'Plagues sweep through densely packed cities, claiming thousands', 'Famines force desperate migrations and social breakdowns', 'Natural disasters reveal underlying social and political tensions', 'Harsh winters test the limits of human endurance and charity', 'Droughts drive conflicts over dwindling water resources',
1031
+ // Innovation & Progress
1032
+ 'Scientific discoveries revolutionize warfare and exploration', 'Technological innovations disrupt traditional crafts and livelihoods', 'Medical breakthroughs save lives but challenge religious views', 'Educational reforms open knowledge to new social classes', 'Engineering marvels push the boundaries of human capability']
1033
+ };
1034
+
1035
+ // Cultural variants within themes
1036
+ this.cultures = {
1037
+ fantasy: {
1038
+ norse: ['The longships sail into the fjord under the northern lights', 'Runes carved in ancient stone whisper forgotten secrets', 'The mead hall echoes with tales of heroic deeds and tragic fates', 'Berserkers rage through the village leaving destruction in their wake', 'The gods themselves seem to take notice of mortal affairs', 'Viking raiders demand tribute or face the wrath of the axe', 'Shamanic visions reveal the threads of fate woven by the Norns', 'The sacred grove hides a portal to the realm of the Aesir', 'Blood oaths bind warriors to quests of honor and vengeance', 'Thunder rolls as Thor\'s hammer strikes down the unworthy'],
1039
+ arabian: ['The desert winds carry secrets from across the golden sands', 'The sultan\'s palace gleams with marble and precious gems', 'Caravans traverse ancient trade routes laden with exotic spices', 'Djinn emerge from bottles granting wishes with treacherous twists', 'The bazaar bustles with merchants hawking forbidden artifacts', 'Flying carpets soar above the minarets of ancient cities', 'Oasis springs hide treasures guarded by serpentine efreet', 'Bedouin tribes honor ancient codes of hospitality and revenge', 'The caliph\'s viziers plot intricate webs of palace intrigue', 'Magic lamps hold genies bound by oaths older than the desert'],
1040
+ celtic: ['The ancient stone circles hum with druidic power', 'Mists roll in from the moors carrying spectral visitors', 'The high king\'s court debates matters of honor and clan loyalty', 'Leprechauns guard pots of gold at the end of rainbows', 'Banshees wail warnings of impending doom from castle towers', 'The green hills hide fairy mounds and trooping fae', 'Druidic circles perform rituals beneath the full moon', 'Clan tartans flutter as warriors clash in honorable combat', 'The sidhe court offers dangerous bargains to mortal visitors', 'Standing stones mark portals to the otherworld'],
1041
+ asian: ['The imperial court buzzes with whispers of dynastic intrigue', 'Paper lanterns float on mist-shrouded temple lakes', 'The great wall stands as both protector and prison', 'Dragon spirits guard ancestral treasures in mountain monasteries', 'Tea ceremonies reveal secrets shared only with trusted allies', 'Samurai honor demands seppuku for failures of duty', 'The emperor\'s astronomers chart the movements of celestial dragons', 'Rice paddies hide the entrances to underworld spirit realms', 'Calligraphy brushes write prophecies on silk scrolls', 'The five elements balance or clash in matters of fate and fortune']
1042
+ },
1043
+ 'sci-fi': {
1044
+ cyberpunk: ['Neon lights reflect off rain-slicked streets in the megacity sprawl', 'Corporate security forces patrol the undercity with neural implants', 'Black market clinics offer experimental augmentations at deadly prices', 'Data runners jack into the net, risking their minds for digital secrets', 'Street gangs control territory with drone swarms and illegal tech', 'Megacorporations wage proxy wars through shell companies and hackers', 'Neural lace interfaces blur the line between human and machine', 'The undergrid pulses with illegal transmissions and rebel broadcasts', 'Synthcoke dealers promise escape from the corporate grind', 'Rogue AIs manifest as ghosts in the machine, manipulating events'],
1045
+ space_opera: ['Star destroyers cast shadows across colonized planets', 'The galactic senate debates matters of interstellar diplomacy', 'Smugglers navigate asteroid fields laden with contraband cargo', 'Alien ambassadors negotiate treaties in crystalline council chambers', 'Hyperspace routes hide pirate ambushes and temporal anomalies', 'The emperor\'s new clothes are actually powered armor', 'Jedi knights wield lightsabers in duels of honor and destiny', 'Space stations orbit gas giants, hubs of trade and intrigue', 'The force guides the worthy through trials of wisdom and combat', 'Rebel alliances form in the shadows of imperial domination'],
1046
+ post_apocalyptic: ['Mutated creatures roam the irradiated wastelands', 'Vault dwellers emerge from underground bunkers into harsh sunlight', 'Caravan masters trade caps for water and ammunition', 'Super mutants patrol ruined highways in power armor', 'The brotherhood preserves technology as holy relics', 'Ghoul settlements offer sanctuary at the cost of sanity', 'Deathclaws stalk the ruins of pre-war cities', 'Enclave remnants cling to old world ideologies', 'Wasteland doctors perform surgery with scavenged medical supplies', 'Tribal shamans commune with the spirits of the old world']
1047
+ },
1048
+ historical: {
1049
+ medieval: ['Knights joust in tournament fields beneath colorful pennants', 'The castle keep stands as bastion against siege engines', 'Peasant revolts spread like wildfire through feudal lands', 'The black death claims lives across town and countryside', 'Alchemists in hidden laboratories seek the philosopher\'s stone', 'Crusader armies march under banners of faith and conquest', 'Courtly love complicates matters of honor and duty', 'The Magna Carta is sealed with wax and noble promises', 'Witch hunters burn heretics at public stake executions', 'Guild masters guard trade secrets with jealous vigilance'],
1050
+ victorian: ['Steam engines power the industrial revolution across soot-stained cities', 'The British Empire spans continents with colonial ambitions', 'Social reformers challenge the rigid class structures of society', 'Spiritualists commune with the dead in dimly lit seances', 'Jack the Ripper stalks the fog-shrouded streets of Whitechapel', 'The industrialists build empires on coal and child labor', 'Suffragettes demand voting rights through peaceful protest', 'Opium dens offer escape from the pressures of imperial duty', 'The great exhibition showcases marvels of modern invention', 'Darwin\'s theories challenge religious doctrines of creation'],
1051
+ ancient_roman: ['Gladiators fight for glory in the Colosseum\'s sandy arena', 'The Roman Senate debates matters of republic and empire', 'Barbarian hordes threaten the borders of civilized lands', 'The aqueducts carry water across vast engineering marvels', 'Political assassinations reshape the corridors of power', 'Slave revolts challenge the foundations of imperial society', 'The gods demand tribute through elaborate public ceremonies', 'Military legions march under eagles forged in sacred fires', 'Patrician families feud over matters of honor and inheritance', 'The imperial palace echoes with plots and betrayals']
1052
+ }
1053
+ };
1054
+
1055
+ // Seasonal and time-based event templates
1056
+ this.seasonalEvents = {
1057
+ spring: {
1058
+ templates: ['BLOOMING_ROMANCE', 'SPRING_FESTIVAL', 'NEW_BEGINNINGS'],
1059
+ modifiers: {
1060
+ happiness: 1.2,
1061
+ romance_events: 1.5,
1062
+ renewal_events: 2.0
1063
+ }
1064
+ },
1065
+ summer: {
1066
+ templates: ['SUMMER_TOURNAMENT', 'BEACH_FESTIVAL', 'HARVEST_PREP'],
1067
+ modifiers: {
1068
+ health: 1.1,
1069
+ activity_events: 1.3,
1070
+ celebration_events: 1.8
1071
+ }
1072
+ },
1073
+ autumn: {
1074
+ templates: ['HARVEST_FESTIVAL', 'AUTUMN_HUNT', 'WINTER_PREP'],
1075
+ modifiers: {
1076
+ wealth: 1.3,
1077
+ preparation_events: 1.6,
1078
+ scarcity_events: 1.2
1079
+ }
1080
+ },
1081
+ winter: {
1082
+ templates: ['WINTER_SOLSTICE', 'HOLIDAY_CELEBRATION', 'SURVIVAL_CHALLENGE'],
1083
+ modifiers: {
1084
+ stress: 1.4,
1085
+ survival_events: 1.8,
1086
+ warmth_events: 2.0,
1087
+ scarcity_events: 1.5
1088
+ }
1089
+ }
1090
+ };
1091
+
1092
+ // Time-based event chains (events that evolve over time)
1093
+ this.timeBasedEventChains = {
1094
+ POLITICAL_UPRISING: {
1095
+ name: 'Political Uprising',
1096
+ description: 'A rebellion builds against oppressive rule',
1097
+ stages: [{
1098
+ day: 1,
1099
+ template: 'RUMORS_OF_DISSENT'
1100
+ }, {
1101
+ day: 7,
1102
+ template: 'PUBLIC_PROTESTS'
1103
+ }, {
1104
+ day: 14,
1105
+ template: 'OPEN_REBELLION'
1106
+ }, {
1107
+ day: 21,
1108
+ template: 'REVOLUTIONARY_CLIMAX'
1109
+ }]
1110
+ },
1111
+ ECONOMIC_COLLAPSE: {
1112
+ name: 'Economic Collapse',
1113
+ description: 'A financial crisis spirals out of control',
1114
+ stages: [{
1115
+ day: 1,
1116
+ template: 'MARKET_UNREST'
1117
+ }, {
1118
+ day: 5,
1119
+ template: 'BANK_RUN'
1120
+ }, {
1121
+ day: 10,
1122
+ template: 'ECONOMIC_CRISIS'
1123
+ }, {
1124
+ day: 15,
1125
+ template: 'SOCIAL_UNREST'
1126
+ }]
1127
+ },
1128
+ MYSTICAL_AWAKENING: {
1129
+ name: 'Mystical Awakening',
1130
+ description: 'Ancient powers stir and manifest',
1131
+ stages: [{
1132
+ day: 1,
1133
+ template: 'STRANGE_OCCURRENCES'
1134
+ }, {
1135
+ day: 3,
1136
+ template: 'MAGICAL_SURGES'
1137
+ }, {
1138
+ day: 7,
1139
+ template: 'ARCANE_MANIFESTATION'
1140
+ }, {
1141
+ day: 10,
1142
+ template: 'MYSTICAL_CLIMAX'
1143
+ }]
1144
+ }
600
1145
  };
601
- context.social_standing = this.calculateSocialStanding(context);
602
- context.power_level = this.calculatePowerLevel(context);
603
- context.life_experience = this.calculateLifeExperience(context);
604
- return context;
605
1146
  }
606
1147
 
607
1148
  /**
608
- * Calculate social standing based on various factors
1149
+ * Get training data for a specific theme and culture
1150
+ * @param {string} theme - The theme to get training data for
1151
+ * @param {string} culture - The cultural variant (optional)
1152
+ * @returns {Array} Training data for the specified theme/culture
1153
+ * @private
1154
+ */
1155
+ getThemeTrainingData(theme, culture) {
1156
+ const baseThemeData = this.themes[theme] || this.themes.fantasy;
1157
+ if (!culture || !this.cultures[theme] || !this.cultures[theme][culture]) {
1158
+ return baseThemeData;
1159
+ }
1160
+ const culturalData = this.cultures[theme][culture];
1161
+ const blendRatio = 0.6;
1162
+ const baseCount = Math.floor(baseThemeData.length * blendRatio);
1163
+ const cultureCount = Math.floor(culturalData.length * (1 - blendRatio));
1164
+ const blendedData = [...baseThemeData.slice(0, baseCount), ...culturalData.slice(0, cultureCount)];
1165
+ return this.chance.shuffle(blendedData);
1166
+ }
1167
+
1168
+ /**
1169
+ * Initialize event chain definitions
1170
+ * @private
1171
+ */
1172
+ initializeChainDefinitions() {
1173
+ return {
1174
+ BANDIT_RISING: {
1175
+ name: 'Bandit Rising',
1176
+ description: 'A bandit threat that escalates over time',
1177
+ stages: [{
1178
+ template: 'BANDIT_AMBUSH',
1179
+ delay: 0,
1180
+ triggerNext: {
1181
+ choice: 'bandit',
1182
+ delay: 3
1183
+ }
1184
+ }, {
1185
+ template: 'BANDIT_KING',
1186
+ delay: 3,
1187
+ triggerNext: {
1188
+ choice: 'hero',
1189
+ delay: 5
1190
+ }
1191
+ }, {
1192
+ template: 'BANDIT_ARMY',
1193
+ delay: 5
1194
+ }]
1195
+ },
1196
+ COURT_SCANDAL_CHAIN: {
1197
+ name: 'Royal Scandal',
1198
+ description: 'A court intrigue that unfolds in multiple acts',
1199
+ stages: [{
1200
+ template: 'COURT_SCANDAL',
1201
+ delay: 0,
1202
+ triggerNext: {
1203
+ choice: 'investigator',
1204
+ delay: 2
1205
+ }
1206
+ }, {
1207
+ template: 'ASSASSINATION_PLOT',
1208
+ delay: 2,
1209
+ triggerNext: {
1210
+ choice: 'intervene',
1211
+ delay: 4
1212
+ }
1213
+ }, {
1214
+ template: 'ROYAL_PURGE',
1215
+ delay: 4
1216
+ }]
1217
+ },
1218
+ CURSE_OF_THE_ARTIFACT: {
1219
+ name: 'Cursed Artifact',
1220
+ description: 'An ancient curse that manifests in stages',
1221
+ stages: [{
1222
+ template: 'ANCIENT_CURSE',
1223
+ delay: 0,
1224
+ triggerNext: {
1225
+ automatic: true,
1226
+ delay: 2
1227
+ }
1228
+ }, {
1229
+ template: 'GHOSTLY_VISITATION',
1230
+ delay: 2,
1231
+ triggerNext: {
1232
+ automatic: true,
1233
+ delay: 3
1234
+ }
1235
+ }, {
1236
+ template: 'FINAL_RECKONING',
1237
+ delay: 3
1238
+ }]
1239
+ },
1240
+ MERCHANT_EMPIRE: {
1241
+ name: 'Rising Merchant Empire',
1242
+ description: 'Building a trade empire with escalating opportunities',
1243
+ stages: [{
1244
+ template: 'TRADE_OPPORTUNITY',
1245
+ delay: 0,
1246
+ triggerNext: {
1247
+ choice: 'invest',
1248
+ delay: 4
1249
+ }
1250
+ }, {
1251
+ template: 'MARKET_CRASH',
1252
+ delay: 4,
1253
+ triggerNext: {
1254
+ choice: 'rebuild',
1255
+ delay: 6
1256
+ }
1257
+ }, {
1258
+ template: 'TRADE_WAR',
1259
+ delay: 6
1260
+ }]
1261
+ }
1262
+ };
1263
+ }
1264
+
1265
+ /**
1266
+ * Start an event chain
1267
+ * @param {string} chainId - ID of the chain to start
1268
+ * @param {Object} playerContext - Player context
1269
+ * @returns {Object|null} First event in the chain or null
1270
+ */
1271
+ startChain(chainId, playerContext = {}) {
1272
+ const chainDef = this.chainDefinitions[chainId];
1273
+ if (!chainDef) return null;
1274
+ const chain = {
1275
+ id: `chain_${Date.now()}_${this.chance.guid().substring(0, 8)}`,
1276
+ definition: chainId,
1277
+ stage: 0,
1278
+ startTime: Date.now(),
1279
+ playerContext: {
1280
+ ...playerContext
1281
+ }
1282
+ };
1283
+ this.activeChains.set(chain.id, chain);
1284
+
1285
+ // Generate first event in chain
1286
+ return this.generateChainEvent(chain);
1287
+ }
1288
+
1289
+ /**
1290
+ * Generate an event from an active chain
1291
+ * @param {Object} chain - Chain object
1292
+ * @returns {Object|null} Generated event or null
1293
+ */
1294
+ generateChainEvent(chain) {
1295
+ const chainDef = this.chainDefinitions[chain.definition];
1296
+ if (!chainDef || chain.stage >= chainDef.stages.length) return null;
1297
+ const stage = chainDef.stages[chain.stage];
1298
+ const template = this.templates[stage.template];
1299
+ if (!template) return null;
1300
+ const context = this.analyzeContext(chain.playerContext);
1301
+ return {
1302
+ id: `event_${chain.id}_${chain.stage}`,
1303
+ title: this.generateDynamicTitle(template, context),
1304
+ description: this.generateRichDescription(template, context),
1305
+ narrative: template.narrative,
1306
+ choices: this.generateContextualChoices(template.choices, context),
1307
+ type: stage.template,
1308
+ consequence: null,
1309
+ context: context,
1310
+ urgency: this.calculateUrgency(template, context),
1311
+ theme: this.determineTheme(template, context),
1312
+ chainId: chain.id,
1313
+ chainStage: chain.stage,
1314
+ isChainEvent: true
1315
+ };
1316
+ }
1317
+
1318
+ /**
1319
+ * Advance an event chain based on player choice
1320
+ * @param {string} chainId - Chain ID
1321
+ * @param {string} choice - Player choice consequence
1322
+ * @returns {Object|null} Next event in chain or null
1323
+ */
1324
+ advanceChain(chainId, choice) {
1325
+ const chain = this.activeChains.get(chainId);
1326
+ if (!chain) return null;
1327
+ const chainDef = this.chainDefinitions[chain.definition];
1328
+ const currentStage = chainDef.stages[chain.stage];
1329
+
1330
+ // Check if current stage triggers next based on choice
1331
+ if (currentStage.triggerNext && currentStage.triggerNext.choice === choice) {
1332
+ chain.stage++;
1333
+ chain.playerContext = {
1334
+ ...chain.playerContext,
1335
+ lastChoice: choice
1336
+ };
1337
+
1338
+ // Schedule next event for game integration
1339
+ if (chain.stage < chainDef.stages.length) {
1340
+ chain.nextEventTime = this.timeSystem.currentDay + currentStage.triggerNext.delay;
1341
+ chain.nextEventTemplate = chainDef.stages[chain.stage].template;
1342
+ // In a real game, save this chain state and check for due events on each game day
1343
+ }
1344
+ return this.generateChainEvent(chain);
1345
+ }
1346
+ return null;
1347
+ }
1348
+
1349
+ /**
1350
+ * Get all active chains
1351
+ * @returns {Array} Array of active chain objects
1352
+ */
1353
+ getActiveChains() {
1354
+ return Array.from(this.activeChains.values());
1355
+ }
1356
+
1357
+ /**
1358
+ * End a chain
1359
+ * @param {string} chainId - Chain ID to end
1360
+ */
1361
+ endChain(chainId) {
1362
+ this.activeChains.delete(chainId);
1363
+ }
1364
+
1365
+ /**
1366
+ * Advance game time and trigger time-based events
1367
+ * @param {number} days - Number of days to advance (default: 1)
1368
+ * @returns {Array} Array of time-based events triggered
1369
+ */
1370
+ advanceTime(days = 1) {
1371
+ const triggeredEvents = [];
1372
+ for (let i = 0; i < days; i++) {
1373
+ this.timeSystem.currentDay++;
1374
+ const seasonLength = 90;
1375
+ const seasonIndex = Math.floor((this.timeSystem.currentDay - 1) / seasonLength) % 4;
1376
+ const seasons = ['spring', 'summer', 'autumn', 'winter'];
1377
+ const newSeason = seasons[seasonIndex];
1378
+ if (newSeason !== this.timeSystem.currentSeason) {
1379
+ this.timeSystem.currentSeason = newSeason;
1380
+ triggeredEvents.push({
1381
+ type: 'seasonal_change',
1382
+ season: newSeason,
1383
+ day: this.timeSystem.currentDay
1384
+ });
1385
+ }
1386
+
1387
+ // Check for time-based event triggers
1388
+ const dayEvents = this.checkTimeBasedEventTriggers();
1389
+ triggeredEvents.push(...dayEvents);
1390
+ }
1391
+ return triggeredEvents;
1392
+ }
1393
+
1394
+ /**
1395
+ * Check for time-based event triggers
1396
+ * @returns {Array} Array of triggered events
1397
+ * @private
1398
+ */
1399
+ checkTimeBasedEventTriggers() {
1400
+ const triggeredEvents = [];
1401
+ for (const [chainId, chainData] of this.timeSystem.timeBasedEvents.entries()) {
1402
+ const daysSinceStart = this.timeSystem.currentDay - chainData.startDay;
1403
+ for (const stage of chainData.chain.stages) {
1404
+ if (stage.day === daysSinceStart && !chainData.completedStages.includes(stage.day)) {
1405
+ chainData.completedStages.push(stage.day);
1406
+ const event = {
1407
+ type: 'time_based_chain',
1408
+ chainId: chainId,
1409
+ stage: stage,
1410
+ day: this.timeSystem.currentDay,
1411
+ template: this.templates[stage.template]
1412
+ };
1413
+ triggeredEvents.push(event);
1414
+ break; // Only trigger one stage per day
1415
+ }
1416
+ }
1417
+ }
1418
+ if (this.chance.bool({
1419
+ likelihood: 10
1420
+ })) {
1421
+ const seasonalData = this.seasonalEvents[this.timeSystem.currentSeason];
1422
+ if (seasonalData && seasonalData.templates.length > 0) {
1423
+ const randomTemplate = this.chance.pickone(seasonalData.templates);
1424
+ if (this.templates[randomTemplate]) {
1425
+ triggeredEvents.push({
1426
+ type: 'seasonal_random',
1427
+ season: this.timeSystem.currentSeason,
1428
+ template: this.templates[randomTemplate],
1429
+ day: this.timeSystem.currentDay
1430
+ });
1431
+ }
1432
+ }
1433
+ }
1434
+ return triggeredEvents;
1435
+ }
1436
+
1437
+ /**
1438
+ * Start a time-based event chain
1439
+ * @param {string} chainName - Name of the chain to start
1440
+ * @returns {boolean} Success status
1441
+ */
1442
+ startTimeBasedChain(chainName) {
1443
+ const chain = this.timeBasedEventChains[chainName];
1444
+ if (!chain) return false;
1445
+ const chainId = `time_chain_${Date.now()}_${this.chance.guid().substring(0, 8)}`;
1446
+ this.timeSystem.timeBasedEvents.set(chainId, {
1447
+ definition: chainName,
1448
+ stage: 0,
1449
+ startDay: this.timeSystem.currentDay,
1450
+ completedStages: new Set()
1451
+ });
1452
+ return true;
1453
+ }
1454
+
1455
+ /**
1456
+ * Get current game time information
1457
+ * @returns {Object} Time system state
1458
+ */
1459
+ getCurrentTime() {
1460
+ var _this$seasonalEvents$;
1461
+ return {
1462
+ day: this.timeSystem.currentDay,
1463
+ season: this.timeSystem.currentSeason,
1464
+ year: Math.ceil(this.timeSystem.currentDay / 360),
1465
+ // Roughly 360 days per year
1466
+ seasonalModifiers: ((_this$seasonalEvents$ = this.seasonalEvents[this.timeSystem.currentSeason]) === null || _this$seasonalEvents$ === void 0 ? void 0 : _this$seasonalEvents$.modifiers) || {}
1467
+ };
1468
+ }
1469
+
1470
+ /**
1471
+ * Advance to the next game day and process any due time-based events
1472
+ * @returns {Array} Array of events that are now ready to trigger
1473
+ */
1474
+ advanceGameDay() {
1475
+ this.timeSystem.currentDay++;
1476
+ const seasonLength = 90;
1477
+ const seasonIndex = Math.floor((this.timeSystem.currentDay - 1) / seasonLength) % 4;
1478
+ const seasons = ['spring', 'summer', 'autumn', 'winter'];
1479
+ const newSeason = seasons[seasonIndex];
1480
+ if (newSeason !== this.timeSystem.currentSeason) {
1481
+ this.timeSystem.currentSeason = newSeason;
1482
+ }
1483
+ const dueEvents = [];
1484
+ for (const [chainId, chainData] of this.timeSystem.timeBasedEvents.entries()) {
1485
+ const chainDef = this.timeBasedEventChains[chainData.definition];
1486
+ if (!chainDef) continue;
1487
+ const daysSinceStart = this.timeSystem.currentDay - chainData.startDay;
1488
+ for (let i = chainData.stage; i < chainDef.stages.length; i++) {
1489
+ const stage = chainDef.stages[i];
1490
+ if (stage.day <= daysSinceStart && !chainData.completedStages.has(i)) {
1491
+ const event = {
1492
+ type: 'time_based_chain',
1493
+ chainId: chainId,
1494
+ template: this.templates[stage.template],
1495
+ day: this.timeSystem.currentDay,
1496
+ chainStage: i,
1497
+ stageDay: stage.day
1498
+ };
1499
+ dueEvents.push(event);
1500
+ chainData.completedStages.add(i);
1501
+ chainData.stage = Math.max(chainData.stage, i + 1);
1502
+ }
1503
+ }
1504
+ }
1505
+ if (this.chance.bool({
1506
+ likelihood: 10
1507
+ })) {
1508
+ const seasonalData = this.seasonalEvents[this.timeSystem.currentSeason];
1509
+ if (seasonalData && seasonalData.templates.length > 0) {
1510
+ const randomTemplate = this.chance.pickone(seasonalData.templates);
1511
+ if (this.templates[randomTemplate]) {
1512
+ dueEvents.push({
1513
+ type: 'seasonal_random',
1514
+ season: this.timeSystem.currentSeason,
1515
+ template: this.templates[randomTemplate],
1516
+ day: this.timeSystem.currentDay
1517
+ });
1518
+ }
1519
+ }
1520
+ }
1521
+ return dueEvents;
1522
+ }
1523
+
1524
+ /**
1525
+ * Get all currently active time-based chains with their status
1526
+ * @returns {Array} Array of chain status objects
1527
+ */
1528
+ getActiveTimeChains() {
1529
+ const activeChains = [];
1530
+ for (const [chainId, chainData] of this.timeSystem.timeBasedEvents.entries()) {
1531
+ const chainDef = this.timeBasedEventChains[chainData.definition];
1532
+ if (chainDef) {
1533
+ activeChains.push({
1534
+ id: chainId,
1535
+ name: chainDef.name,
1536
+ description: chainDef.description,
1537
+ currentStage: chainData.stage,
1538
+ totalStages: chainDef.stages.length,
1539
+ nextEventDay: chainData.nextEventTime || null,
1540
+ nextEventTemplate: chainData.nextEventTemplate || null,
1541
+ startDay: chainData.startDay,
1542
+ daysActive: this.timeSystem.currentDay - chainData.startDay
1543
+ });
1544
+ }
1545
+ }
1546
+ return activeChains;
1547
+ }
1548
+
1549
+ /**
1550
+ * Get game state for saving (includes time system and active chains)
1551
+ * @returns {Object} Serializable game state
1552
+ */
1553
+ getGameState() {
1554
+ return {
1555
+ timeSystem: {
1556
+ currentDay: this.timeSystem.currentDay,
1557
+ currentSeason: this.timeSystem.currentSeason,
1558
+ gameYear: Math.ceil(this.timeSystem.currentDay / 360)
1559
+ },
1560
+ activeChains: Array.from(this.timeSystem.timeBasedEvents.entries()).map(([id, data]) => ({
1561
+ id,
1562
+ definition: data.definition,
1563
+ stage: data.stage,
1564
+ startDay: data.startDay,
1565
+ nextEventTime: data.nextEventTime || null,
1566
+ nextEventTemplate: data.nextEventTemplate || null,
1567
+ completedStages: [...data.completedStages]
1568
+ })),
1569
+ theme: this.theme,
1570
+ culture: this.culture
1571
+ };
1572
+ }
1573
+
1574
+ /**
1575
+ * Load game state (restores time system and active chains)
1576
+ * @param {Object} gameState - State object from getGameState()
1577
+ * @returns {boolean} Success status
1578
+ */
1579
+ loadGameState(gameState) {
1580
+ try {
1581
+ if (gameState.timeSystem) {
1582
+ this.timeSystem.currentDay = gameState.timeSystem.currentDay || 1;
1583
+ this.timeSystem.currentSeason = gameState.timeSystem.currentSeason || 'spring';
1584
+ this.timeSystem.gameYear = gameState.timeSystem.gameYear || 1;
1585
+ }
1586
+ this.timeSystem.timeBasedEvents.clear();
1587
+ if (gameState.activeChains) {
1588
+ gameState.activeChains.forEach(chainData => {
1589
+ this.timeSystem.timeBasedEvents.set(chainData.id, {
1590
+ definition: chainData.definition,
1591
+ stage: chainData.stage,
1592
+ startDay: chainData.startDay,
1593
+ nextEventTime: chainData.nextEventTime,
1594
+ nextEventTemplate: chainData.nextEventTemplate,
1595
+ completedStages: new Set(chainData.completedStages || [])
1596
+ });
1597
+ });
1598
+ }
1599
+ if (gameState.theme) this.theme = gameState.theme;
1600
+ if (gameState.culture) this.culture = gameState.culture;
1601
+ return true;
1602
+ } catch (error) {
1603
+ console.warn('Failed to load game state:', error.message);
1604
+ return false;
1605
+ }
1606
+ }
1607
+
1608
+ /**
1609
+ * Generate a time-aware event that considers current season and time-based factors
1610
+ * @param {Object} playerContext - Player context
1611
+ * @returns {Object} Generated event with time awareness
1612
+ */
1613
+ generateTimeAwareEvent(playerContext = {}) {
1614
+ const context = this.analyzeContext(playerContext);
1615
+ const currentTime = this.getCurrentTime();
1616
+ const seasonalMods = currentTime.seasonalModifiers;
1617
+ Object.keys(seasonalMods).forEach(key => {
1618
+ if (context[key] !== undefined) {
1619
+ context[key] *= seasonalMods[key];
1620
+ }
1621
+ });
1622
+ const template = this.selectTemplate(context);
1623
+ const scaledChoices = this.scaleEffectsForDifficulty(this.generateContextualChoices(template.choices, context), context);
1624
+ return {
1625
+ id: `event_${Date.now()}_${this.chance.guid().substring(0, 8)}`,
1626
+ title: this.generateDynamicTitle(template, context),
1627
+ description: this.generateRichDescription(template, context),
1628
+ narrative: template.narrative,
1629
+ choices: scaledChoices,
1630
+ type: Object.keys(this.templates).find(key => this.templates[key] === template),
1631
+ consequence: null,
1632
+ context: context,
1633
+ urgency: this.calculateUrgency(template, context),
1634
+ theme: this.determineTheme(template, context),
1635
+ difficulty: this.calculateDifficultyTier(context.power_level || 0),
1636
+ timeInfo: currentTime
1637
+ };
1638
+ }
1639
+
1640
+ /**
1641
+ * Register a custom event template
1642
+ * @param {string} templateId - Unique identifier for the template
1643
+ * @param {Object} template - Event template object
1644
+ * @param {string} template.title - Event title
1645
+ * @param {string} template.narrative - Event description
1646
+ * @param {Array} template.choices - Array of choice objects
1647
+ * @param {boolean} override - Whether to override existing template
1648
+ * @returns {boolean} Success status
1649
+ */
1650
+ registerEventTemplate(templateId, template, override = false) {
1651
+ if (!templateId || typeof templateId !== 'string') {
1652
+ console.warn('Invalid template ID provided');
1653
+ return false;
1654
+ }
1655
+ if (!template || !template.title || !template.narrative || !Array.isArray(template.choices)) {
1656
+ console.warn('Invalid template structure. Required: title, narrative, choices[]');
1657
+ return false;
1658
+ }
1659
+ if (this.templates[templateId] && !override) {
1660
+ console.warn(`Template '${templateId}' already exists. Use override=true to replace it.`);
1661
+ return false;
1662
+ }
1663
+ for (const choice of template.choices) {
1664
+ if (!choice.text || typeof choice.text !== 'string') {
1665
+ console.warn(`Invalid choice structure in template '${templateId}': missing 'text'`);
1666
+ return false;
1667
+ }
1668
+ }
1669
+ this.templates[templateId] = {
1670
+ title: template.title,
1671
+ narrative: template.narrative,
1672
+ choices: template.choices
1673
+ };
1674
+ if (!this.customTemplates) {
1675
+ this.customTemplates = new Set();
1676
+ }
1677
+ this.customTemplates.add(templateId);
1678
+ return true;
1679
+ }
1680
+
1681
+ /**
1682
+ * Unregister a custom event template
1683
+ * @param {string} templateId - Template ID to remove
1684
+ * @returns {boolean} Success status
1685
+ */
1686
+ unregisterEventTemplate(templateId) {
1687
+ if (!this.customTemplates || !this.customTemplates.has(templateId)) {
1688
+ console.warn(`Template '${templateId}' is not a registered custom template`);
1689
+ return false;
1690
+ }
1691
+ delete this.templates[templateId];
1692
+ this.customTemplates.delete(templateId);
1693
+ return true;
1694
+ }
1695
+
1696
+ /**
1697
+ * Get all registered custom templates
1698
+ * @returns {Array} Array of custom template IDs
1699
+ */
1700
+ getCustomTemplates() {
1701
+ return this.customTemplates ? Array.from(this.customTemplates) : [];
1702
+ }
1703
+
1704
+ /**
1705
+ * Add custom training data for Markov chain generation
1706
+ * @param {Array|string} data - Training data (array of strings or single string)
1707
+ * @param {string} category - Category name for organization
1708
+ * @returns {boolean} Success status
1709
+ */
1710
+ addCustomTrainingData(data, category) {
1711
+ if (category === undefined) category = 'custom';
1712
+ if (!data) return false;
1713
+ if (!this.customTrainingData) {
1714
+ this.customTrainingData = {};
1715
+ }
1716
+ if (!this.customTrainingData[category]) {
1717
+ this.customTrainingData[category] = [];
1718
+ }
1719
+ const dataArray = Array.isArray(data) ? data : [data];
1720
+ const validData = dataArray.filter(function (item) {
1721
+ return typeof item === 'string' && item.trim().length > 0;
1722
+ });
1723
+ if (validData.length === 0) return false;
1724
+ this.customTrainingData[category] = this.customTrainingData[category].concat(validData);
1725
+ try {
1726
+ const allData = this.getCombinedTrainingData();
1727
+ this.markovGenerator.addData(allData);
1728
+ return true;
1729
+ } catch (error) {
1730
+ return false;
1731
+ }
1732
+ }
1733
+
1734
+ /**
1735
+ * Remove custom training data
1736
+ * @param {string} category - Category to remove (removes all data in category)
1737
+ * @returns {boolean} Success status
1738
+ */
1739
+ removeTrainingData(category) {
1740
+ if (!this.customTrainingData || !this.customTrainingData[category]) {
1741
+ console.warn(`Training data category '${category}' not found`);
1742
+ return false;
1743
+ }
1744
+ delete this.customTrainingData[category];
1745
+ const allData = this.getCombinedTrainingData();
1746
+ try {
1747
+ this.markovGenerator.addData(allData);
1748
+ } catch (error) {
1749
+ console.warn('Failed to retrain Markov generator after removal:', error.message);
1750
+ return false;
1751
+ }
1752
+ return true;
1753
+ }
1754
+
1755
+ /**
1756
+ * Get combined training data (base + custom)
1757
+ * @returns {Array} Combined training data array
1758
+ * @private
1759
+ */
1760
+ getCombinedTrainingData() {
1761
+ let combinedData = [];
1762
+ const themeData = this.getThemeTrainingData(this.theme || 'fantasy', this.culture);
1763
+ combinedData.push(...themeData);
1764
+ if (this.customTrainingData) {
1765
+ Object.values(this.customTrainingData).forEach(categoryData => {
1766
+ combinedData.push(...categoryData);
1767
+ });
1768
+ }
1769
+ return combinedData;
1770
+ }
1771
+
1772
+ /**
1773
+ * Register a custom event chain
1774
+ * @param {string} chainId - Unique identifier for the chain
1775
+ * @param {Object} chainConfig - Chain configuration
1776
+ * @param {string} chainConfig.name - Display name for the chain
1777
+ * @param {string} chainConfig.description - Chain description
1778
+ * @param {Array} chainConfig.stages - Array of stage objects
1779
+ * @param {boolean} override - Whether to override existing chain
1780
+ * @returns {boolean} Success status
1781
+ */
1782
+ registerEventChain(chainId, chainConfig, override = false) {
1783
+ if (!chainId || typeof chainId !== 'string') {
1784
+ console.warn('Invalid chain ID provided');
1785
+ return false;
1786
+ }
1787
+ if (!chainConfig || !chainConfig.name || !chainConfig.description || !Array.isArray(chainConfig.stages)) {
1788
+ console.warn('Invalid chain configuration. Required: name, description, stages[]');
1789
+ return false;
1790
+ }
1791
+ if (this.timeBasedEventChains[chainId] && !override) {
1792
+ console.warn(`Chain '${chainId}' already exists. Use override=true to replace it.`);
1793
+ return false;
1794
+ }
1795
+ for (let i = 0; i < chainConfig.stages.length; i++) {
1796
+ const stage = chainConfig.stages[i];
1797
+ if (!stage.day || !stage.template) {
1798
+ console.warn(`Invalid stage ${i} in chain '${chainId}': missing 'day' or 'template'`);
1799
+ return false;
1800
+ }
1801
+ if (!this.templates[stage.template]) {
1802
+ console.warn(`Template '${stage.template}' in chain '${chainId}' does not exist`);
1803
+ return false;
1804
+ }
1805
+ }
1806
+ this.timeBasedEventChains[chainId] = {
1807
+ name: chainConfig.name,
1808
+ description: chainConfig.description,
1809
+ stages: chainConfig.stages
1810
+ };
1811
+ if (!this.customChains) {
1812
+ this.customChains = new Set();
1813
+ }
1814
+ this.customChains.add(chainId);
1815
+ return true;
1816
+ }
1817
+
1818
+ /**
1819
+ * Unregister a custom event chain
1820
+ * @param {string} chainId - Chain ID to remove
1821
+ * @returns {boolean} Success status
1822
+ */
1823
+ unregisterEventChain(chainId) {
1824
+ if (!this.customChains || !this.customChains.has(chainId)) {
1825
+ console.warn(`Chain '${chainId}' is not a registered custom chain`);
1826
+ return false;
1827
+ }
1828
+ delete this.timeBasedEventChains[chainId];
1829
+ this.customChains.delete(chainId);
1830
+ for (const [instanceId, chainData] of this.timeSystem.timeBasedEvents.entries()) {
1831
+ if (chainData.chain === this.timeBasedEventChains[chainId]) {
1832
+ this.timeSystem.timeBasedEvents.delete(instanceId);
1833
+ }
1834
+ }
1835
+ return true;
1836
+ }
1837
+
1838
+ /**
1839
+ * Get all registered custom chains
1840
+ * @returns {Array} Array of custom chain IDs
1841
+ */
1842
+ getCustomChains() {
1843
+ return this.customChains ? Array.from(this.customChains) : [];
1844
+ }
1845
+
1846
+ /**
1847
+ * Export all custom content for backup/sharing
1848
+ * @returns {Object} Exported custom content
1849
+ */
1850
+ exportCustomContent() {
1851
+ return {
1852
+ templates: Array.from(this.customTemplates || []).reduce((obj, id) => {
1853
+ obj[id] = this.templates[id];
1854
+ return obj;
1855
+ }, {}),
1856
+ trainingData: this.customTrainingData || {},
1857
+ chains: Array.from(this.customChains || []).reduce((obj, id) => {
1858
+ obj[id] = this.timeBasedEventChains[id];
1859
+ return obj;
1860
+ }, {})
1861
+ };
1862
+ }
1863
+
1864
+ /**
1865
+ * Import custom content from export
1866
+ * @param {Object} content - Exported custom content
1867
+ * @param {boolean} override - Whether to override existing content
1868
+ * @returns {Object} Import results
1869
+ */
1870
+ importCustomContent(content, override = false) {
1871
+ const results = {
1872
+ templates: {
1873
+ success: 0,
1874
+ failed: 0
1875
+ },
1876
+ trainingData: {
1877
+ success: 0,
1878
+ failed: 0
1879
+ },
1880
+ chains: {
1881
+ success: 0,
1882
+ failed: 0
1883
+ }
1884
+ };
1885
+ if (content.templates) {
1886
+ Object.entries(content.templates).forEach(([id, template]) => {
1887
+ if (this.registerEventTemplate(id, template, override)) {
1888
+ results.templates.success++;
1889
+ } else {
1890
+ results.templates.failed++;
1891
+ }
1892
+ });
1893
+ }
1894
+ if (content.trainingData) {
1895
+ Object.entries(content.trainingData).forEach(([category, data]) => {
1896
+ if (this.addCustomTrainingData(data, category)) {
1897
+ results.trainingData.success++;
1898
+ } else {
1899
+ results.trainingData.failed++;
1900
+ }
1901
+ });
1902
+ }
1903
+ if (content.chains) {
1904
+ Object.entries(content.chains).forEach(([id, chain]) => {
1905
+ if (this.registerEventChain(id, chain, override)) {
1906
+ results.chains.success++;
1907
+ } else {
1908
+ results.chains.failed++;
1909
+ }
1910
+ });
1911
+ }
1912
+ return results;
1913
+ }
1914
+
1915
+ /**
1916
+ * Analyze and enrich player context for sophisticated event generation
1917
+ * @private
1918
+ */
1919
+ analyzeContext(playerContext) {
1920
+ const ctx = playerContext || {};
1921
+ const context = {
1922
+ age: ctx.age || 16,
1923
+ wealth: ctx.gold || 0,
1924
+ influence: ctx.influence || 0,
1925
+ reputation: ctx.reputation || 0,
1926
+ career: ctx.career || null,
1927
+ skills: ctx.skills || {},
1928
+ relationships: ctx.relationships || [],
1929
+ location: ctx.location || this.generateLocation(),
1930
+ season: ctx.season || this.timeSystem.currentSeason,
1931
+ health: ctx.health || 100,
1932
+ stress: ctx.stress || 0,
1933
+ happiness: ctx.happiness || 50,
1934
+ karma: ctx.karma || 0,
1935
+ faith: ctx.faith || 0,
1936
+ vices: ctx.vices || [],
1937
+ secrets: ctx.secrets || [],
1938
+ ambitions: ctx.ambitions || []
1939
+ };
1940
+ context.social_standing = this.calculateSocialStanding(context);
1941
+ context.power_level = this.calculatePowerLevel(context);
1942
+ context.life_experience = this.calculateLifeExperience(context);
1943
+ return context;
1944
+ }
1945
+
1946
+ /**
1947
+ * Calculate social standing based on various factors
609
1948
  * @private
610
1949
  */
611
1950
  calculateSocialStanding(context) {
@@ -633,6 +1972,94 @@ class RPGEventGenerator {
633
1972
  return Math.max(0, power);
634
1973
  }
635
1974
 
1975
+ /**
1976
+ * Calculate appropriate difficulty tier based on player power level
1977
+ * @param {number} powerLevel - Player's calculated power level
1978
+ * @returns {string} Difficulty tier ('easy', 'normal', 'hard', 'legendary')
1979
+ */
1980
+ calculateDifficultyTier(powerLevel) {
1981
+ if (powerLevel <= 50) return 'easy';
1982
+ if (powerLevel <= 150) return 'normal';
1983
+ if (powerLevel <= 300) return 'hard';
1984
+ return 'legendary';
1985
+ }
1986
+
1987
+ /**
1988
+ * Scale event effects based on difficulty and player power level
1989
+ * @param {Array} choices - Event choices with effects
1990
+ * @param {Object} context - Player context
1991
+ * @returns {Array} Scaled choices
1992
+ */
1993
+ scaleEffectsForDifficulty(choices, context) {
1994
+ const powerLevel = context.power_level || 0;
1995
+ const difficultyTier = this.calculateDifficultyTier(powerLevel);
1996
+ const settings = this.difficultySettings[difficultyTier];
1997
+ return choices.map(choice => {
1998
+ const scaledChoice = {
1999
+ ...choice
2000
+ };
2001
+ if (choice.effect) {
2002
+ scaledChoice.effect = {
2003
+ ...choice.effect
2004
+ };
2005
+ Object.keys(scaledChoice.effect).forEach(key => {
2006
+ const value = scaledChoice.effect[key];
2007
+ if (Array.isArray(value)) {
2008
+ if (key.includes('gold') || key.includes('influence') || key.includes('reputation') || key.includes('knowledge') || key.includes('karma')) {
2009
+ scaledChoice.effect[key] = value.map(v => Math.round(v * settings.rewardMultiplier));
2010
+ } else if (key.includes('health') || key.includes('stress')) {
2011
+ scaledChoice.effect[key] = value.map(v => Math.round(v * settings.penaltyMultiplier));
2012
+ }
2013
+ }
2014
+ });
2015
+ }
2016
+ return scaledChoice;
2017
+ });
2018
+ }
2019
+
2020
+ /**
2021
+ * Adjust template weights based on appropriate challenge level
2022
+ * @param {Object} baseWeights - Base template weights
2023
+ * @param {Object} context - Player context
2024
+ * @returns {Object} Adjusted weights
2025
+ */
2026
+ adjustWeightsForDifficulty(baseWeights, context) {
2027
+ const powerLevel = context.power_level || 0;
2028
+ const difficultyTier = this.calculateDifficultyTier(powerLevel);
2029
+ const challengeRatings = {
2030
+ COURT_SCANDAL: 3,
2031
+ NOBLE_DUEL: 4,
2032
+ THIEVES_GUILD: 5,
2033
+ BLACKMAIL_OPPORTUNITY: 4,
2034
+ ANCIENT_CURSE: 6,
2035
+ GHOSTLY_VISITATION: 5,
2036
+ FORBIDDEN_LOVE: 3,
2037
+ FAMILY_SECRET: 3,
2038
+ LOST_CIVILIZATION: 7,
2039
+ BANDIT_KING: 6,
2040
+ MARKET_CRASH: 4,
2041
+ TRADE_WAR: 5,
2042
+ DESERTION_TEMPTATION: 2,
2043
+ MERCENARY_CONTRACT: 5
2044
+ };
2045
+ const adjustedWeights = {
2046
+ ...baseWeights
2047
+ };
2048
+ Object.keys(adjustedWeights).forEach(templateKey => {
2049
+ const rating = challengeRatings[templateKey] || 5;
2050
+ const powerRatio = powerLevel / 100; // Normalize power level
2051
+
2052
+ if (rating < powerRatio + 2) {
2053
+ adjustedWeights[templateKey] *= 0.5;
2054
+ } else if (rating >= powerRatio + 2 && rating <= powerRatio + 5) {
2055
+ adjustedWeights[templateKey] *= 1.5;
2056
+ } else if (rating > powerRatio + 6) {
2057
+ adjustedWeights[templateKey] *= 0.3;
2058
+ }
2059
+ });
2060
+ return adjustedWeights;
2061
+ }
2062
+
636
2063
  /**
637
2064
  * Calculate life experience factor
638
2065
  * @private
@@ -676,8 +2103,9 @@ class RPGEventGenerator {
676
2103
  baseWeights[key] = this.getBaseWeightForTemplate(key, context);
677
2104
  });
678
2105
  this.applyContextModifiers(baseWeights, context);
679
- const totalWeight = Object.values(baseWeights).reduce((sum, w) => sum + w, 0);
680
- const normalizedWeights = Object.values(baseWeights).map(w => w / totalWeight);
2106
+ const difficultyAdjustedWeights = this.adjustWeightsForDifficulty(baseWeights, context);
2107
+ const totalWeight = Object.values(difficultyAdjustedWeights).reduce((sum, w) => sum + w, 0);
2108
+ const normalizedWeights = Object.values(difficultyAdjustedWeights).map(w => w / totalWeight);
681
2109
  return normalizedWeights;
682
2110
  }
683
2111