Skip to content

Commit

Permalink
Added lore browser for quest memos
Browse files Browse the repository at this point in the history
  • Loading branch information
jwvhewitt committed Jan 12, 2024
1 parent c33fdcb commit 7bf3e1c
Show file tree
Hide file tree
Showing 9 changed files with 318 additions and 154 deletions.
9 changes: 9 additions & 0 deletions game/content/ghcutscene.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,14 @@ def __call__(self,camp,do_rollout=True):
pbge.alert_display(myviz.render)


def alert_with_grammar(camp, text):
# Do an alert display, but with grammar tokens correctly converted.
mygrammar = pbge.dialogue.grammar.Grammar()
pbge.dialogue.GRAMMAR_BUILDER(mygrammar,camp,camp.pc,camp.pc)
altered_text = pbge.dialogue.grammar.convert_tokens(text,mygrammar)
pbge.alert(altered_text)


class SimpleMonologueMenu(pbge.rpgmenu.Menu):
# Useful for times when you don't want or need to invoke the full conversation thingamajig.
def __init__(self,text,npc,camp):
Expand Down Expand Up @@ -220,6 +228,7 @@ def AddTagBasedLancemateMenuItem(mymenu: pbge.rpgmenu.Menu, msg, value, camp, ne
mymenu.items.append(ghdialogue.ghdview.LancemateConvoItem(true_msg, value, desc=None, menu=mymenu, npc=mylm))
return mylm


def AddSkillBasedLancemateMenuItem(mymenu: pbge.rpgmenu.Menu, msg, value, camp: gears.GearHeadCampaign, stat_id, skill_id, rank, difficulty=gears.stats.DIFFICULTY_AVERAGE, pc_msg=None, no_random=False):
# Add an item to this menu where a lancemate suggests something. Designed to be used with the above
# SimpleMonologueMenu, but really it can be used with any menu.
Expand Down
336 changes: 190 additions & 146 deletions game/content/ghplots/ghquests.py

Large diffs are not rendered by default.

9 changes: 9 additions & 0 deletions game/content/ghplots/warplots.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def custom_init(self, nart):
quests.TEXT_LORE_TOPIC: "the state of {METROSCENE}".format(**self.elements),
quests.TEXT_LORE_SELFDISCOVERY: "You learned that {RESISTANCE_FACTION} is working against {OCCUPIER} in {METROSCENE}.".format(**self.elements),
quests.TEXT_LORE_TARGET_TOPIC: "{RESISTANCE_FACTION}'s rebellion".format(**self.elements),
quests.TEXT_LORE_MEMO: "{OCCUPIER} is fortifying their position in {METROSCENE}.".format(**self.elements),
}, involvement = ghchallenges.InvolvedMetroFactionNPCs(
self.elements["METROSCENE"], self.elements["OCCUPIER"]
), priority=True
Expand All @@ -87,6 +88,7 @@ def custom_init(self, nart):
quests.TEXT_LORE_TOPIC: "{OCCUPIER}'s occupation of {METROSCENE}".format(**self.elements),
quests.TEXT_LORE_SELFDISCOVERY: "You learned that there is a resistance dedicated to ousting {OCCUPIER} from {METROSCENE}.".format(**self.elements),
quests.TEXT_LORE_TARGET_TOPIC: "{OCCUPIER}'s occupation of {METROSCENE}".format(**self.elements),
quests.TEXT_LORE_MEMO: "There is a resistance opposing {OCCUPIER}'s occupation of {METROSCENE}.".format(**self.elements),
}, involvement=ghchallenges.InvolvedMetroNoFriendToFactionNPCs(
self.elements["METROSCENE"], self.elements["OCCUPIER"]
), priority=True
Expand All @@ -95,6 +97,7 @@ def custom_init(self, nart):
)

myquest = self.register_element(quests.QUEST_ELEMENT_ID, quests.Quest(
"{OCCUPIER} is attempting to fortify its position in {METROSCENE}.".format(**self.elements),
outcomes=(oc1, oc2), end_on_loss=True
))
myquest.build(nart, self)
Expand Down Expand Up @@ -141,6 +144,7 @@ def custom_init(self, nart):
quests.TEXT_LORE_TOPIC: "the state of {METROSCENE}".format(**self.elements),
quests.TEXT_LORE_SELFDISCOVERY: "You learned that {RESISTANCE_FACTION} is working against {OCCUPIER} in {METROSCENE}.".format(**self.elements),
quests.TEXT_LORE_TARGET_TOPIC: "{RESISTANCE_FACTION}'s rebellion".format(**self.elements),
quests.TEXT_LORE_MEMO: "{OCCUPIER} is attempting to crush all dissent in {METROSCENE}.".format(**self.elements),
}, involvement = ghchallenges.InvolvedMetroFactionNPCs(
self.elements["METROSCENE"], self.elements["OCCUPIER"]
), priority=True
Expand All @@ -160,6 +164,7 @@ def custom_init(self, nart):
quests.TEXT_LORE_TOPIC: "{OCCUPIER}'s occupation of {METROSCENE}".format(**self.elements),
quests.TEXT_LORE_SELFDISCOVERY: "You learned that the people of {METROSCENE} must unite to oust {OCCUPIER}.".format(**self.elements),
quests.TEXT_LORE_TARGET_TOPIC: "{OCCUPIER}'s occupation of {METROSCENE}".format(**self.elements),
quests.TEXT_LORE_MEMO: "There is a resistance opposing {OCCUPIER}'s occupation of {METROSCENE}.".format(**self.elements),
}, involvement=ghchallenges.InvolvedMetroNoFriendToFactionNPCs(
self.elements["METROSCENE"], self.elements["OCCUPIER"]
), priority=True
Expand All @@ -168,6 +173,7 @@ def custom_init(self, nart):
)

myquest = self.register_element(quests.QUEST_ELEMENT_ID, quests.Quest(
"{OCCUPIER} is attempting to crush all dissent in {METROSCENE}.".format(**self.elements),
outcomes=(oc1, oc2), end_on_loss=True
))
myquest.build(nart, self)
Expand Down Expand Up @@ -219,6 +225,7 @@ def custom_init(self, nart):
quests.TEXT_LORE_TOPIC: "the state of {METROSCENE}".format(**self.elements),
quests.TEXT_LORE_SELFDISCOVERY: "You learned that {RESISTANCE_FACTION} is working against {OCCUPIER} in {METROSCENE}.".format(**self.elements),
quests.TEXT_LORE_TARGET_TOPIC: "{RESISTANCE_FACTION}'s rebellion".format(**self.elements),
quests.TEXT_LORE_MEMO: "{OCCUPIER} has placed {METROSCENE} under martial law.".format(**self.elements),
}, involvement = ghchallenges.InvolvedMetroFactionNPCs(
self.elements["METROSCENE"], self.elements["OCCUPIER"]
), priority=True
Expand All @@ -238,6 +245,7 @@ def custom_init(self, nart):
quests.TEXT_LORE_TOPIC: "{OCCUPIER}'s occupation of {METROSCENE}".format(**self.elements),
quests.TEXT_LORE_SELFDISCOVERY: "You learned that there is a resistance dedicated to ousting {OCCUPIER} from {METROSCENE}.".format(**self.elements),
quests.TEXT_LORE_TARGET_TOPIC: "{OCCUPIER}'s occupation of {METROSCENE}".format(**self.elements),
quests.TEXT_LORE_MEMO: "There is a resistance opposing {OCCUPIER}'s occupation of {METROSCENE}.".format(**self.elements),
}, involvement=ghchallenges.InvolvedMetroNoFriendToFactionNPCs(
self.elements["METROSCENE"], self.elements["OCCUPIER"]
), priority=True
Expand All @@ -246,6 +254,7 @@ def custom_init(self, nart):
)

myquest = self.register_element(quests.QUEST_ELEMENT_ID, quests.Quest(
"{OCCUPIER} has imposed martial law in {METROSCENE}.".format(**self.elements),
outcomes=(oc1, oc2), end_on_loss=True
))
myquest.build(nart, self)
Expand Down
7 changes: 7 additions & 0 deletions game/ghdialogue/ghgrammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -1181,6 +1181,13 @@
],
},

"[DISCOVERY_AFTER_MECHA_COMBAT]": {
Default: [
"After the battle, you make an interesting discovery.",
"You find some useful information from the navcomp of one of the defeated mecha."
],
},

"[DISTRACTION]": {
Default: ["[LOOK_AT_THIS] A [adjective] [noun]!"
],
Expand Down
4 changes: 4 additions & 0 deletions history.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
* Plots can contain extensions
* Added lore browser to memo system
* Refactored quest system to be entirely QuestLore based
* Okapi puzzle memo now uses same font (medium) as other memos
* Fixed world map encounters bug
* Burst fire weapons have increased thrill power
* Intercept weapons have their thrill power lowered
Expand Down
10 changes: 9 additions & 1 deletion pbge/campaign.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,15 @@ def all_contents(self, thing, check_subscenes=True, search_path=None, check_temp
yield tt

def get_memos(self):
mymemos = [p.memo for p in self.active_plots() if p.memo]
mymemos = list()
for p in self.active_plots():
if p.memo:
mymemos.append(p.memo)
for ex in p.extensions:
if hasattr(ex, "get_memo"):
exmemo = ex.get_memo()
if exmemo:
mymemos.append(exmemo)
for c in self.get_active_challenges():
cmemo = c.get_memo()
if cmemo:
Expand Down
2 changes: 1 addition & 1 deletion pbge/challenges.py
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ def __str__(self):
def get_widget(self, memobrowser, camp):
mylabel = widgets.LabelWidget(
memobrowser.dx, memobrowser.dy, memobrowser.w, memobrowser.h, text=str(self),
data=memobrowser, justify=0)
data=memobrowser, justify=0, font=my_state.medium_font)
if not self.challenge.is_won():
mybutton = widgets.LabelWidget(
-75, 20, 150, 24, text="Examine Clues", draw_border=True, justify=0, border=widgets.widget_border_on,
Expand Down
4 changes: 4 additions & 0 deletions pbge/plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,14 @@ def __init__(self, nart, pstate):
"""Initialize + install this plot, or raise PlotError"""
# nart = The Narrative object
# pstate = The current plot state
# New in v0.960: extensions! A list of extra bits that can add memos and other stuff.

# Inherit the plot state.
self.adv = pstate.adv
self.rank = pstate.rank or self.rank
self.elements = pstate.elements.copy()
self.subplots = dict()
self.extensions = list()
self.memo = None

# Increment the usage count, for getting info on plot numbers!
Expand Down Expand Up @@ -500,6 +502,8 @@ def __setstate__(self, state):
self.__dict__.update(state)
if "_rumor_memo_delivered" not in state:
self._rumor_memo_delivered = False
if "extensions" not in state:
self.extensions = list()


class NarrativeRequest(object):
Expand Down
91 changes: 85 additions & 6 deletions pbge/quests.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
# A Quest is the opposite of a story- it is a randomly generated narrative with a defined ending.

from . import plots, dialogue
from . import plots, widgets, my_state, frects, image, wait_event, TIMEREVENT
import pygame
import random
import collections
from types import MethodType

VERB_DEFEAT = "DEFEAT"

Expand All @@ -26,6 +25,7 @@
TEXT_LORE_INFO = "[QUEST_LORE_INFO]" # The lore is revealed to the PC; independent clause
TEXT_LORE_TOPIC = "[QUEST_LORE_TOPIC]" # The topic for the PC's inquiry into the lore; noun phrase
TEXT_LORE_SELFDISCOVERY = "[QUEST_LORE_SELFDISCOVERY]" # A sentence for when the PC discovers the info themselves
TEXT_LORE_MEMO = "[TEXT_LORE_MEMO]" # A sentence for use in the lore browser.

# Optional Lore
TEXT_LORE_TARGET_TOPIC = "[QUEST_LORE_TARGET_TOPIC]" # Why are the lore keys formatted like grammar tokens?
Expand Down Expand Up @@ -156,10 +156,13 @@ class Quest:
# the provided maximum chain length which connects all the outcomes to a single beginning state.
# The plot creating the quest needs to call the build function.
# The quest plots need to call the extend function.
# *** A single plot can only build a single quest. This is because the quest modifies the plot. If you need more
# than one quest, use more than one plot.
def __init__(
self, outcomes, task_ident=DEFAULT_QUEST_TASK, conclusion_series=DEFAULT_QUEST_CONCLUSION_SERIES,
self, desc, outcomes, task_ident=DEFAULT_QUEST_TASK, conclusion_series=DEFAULT_QUEST_CONCLUSION_SERIES,
course_length=3, end_on_loss=True, lore_handler=DEFAULT_QUEST_LORE_HANDLER
):
self.desc = desc.capitalize()
self.outcomes = list(outcomes)
self.task_ident = task_ident
if not isinstance(conclusion_series, (list, tuple)):
Expand All @@ -176,6 +179,7 @@ def __init__(

def build(self, nart, root_plot: plots.Plot):
# Start constructing a quest starting with root_plot as the main controller.
root_plot.extensions.append(self)
random.shuffle(self.outcomes)
for numa, outc in enumerate(self.outcomes):
primary_conclusion = None
Expand Down Expand Up @@ -214,6 +218,7 @@ def build(self, nart, root_plot: plots.Plot):

self.add_quest_lore_handler(root_plot, nart)
del self._not_lockable_lore
print(self.all_plots)

def add_quest_lore_handler(self, root_plot, nart):
lore_set = set()
Expand Down Expand Up @@ -313,15 +318,42 @@ def will_not_cause_lore_blockage(self, new_rec):
break

# I did not mean the final line of this function to sound like something you'd hear a mansplainer say, but...
#if all_plots:
# print("LOre Blockage: {} {}".format([str(l) for l in new_rec.needed_lore], [str(l) for l in new_rec.lore_to_reveal]))
return not all_plots

@property
# People might want to look at the revealed lore set, but no touching.
def revealed_lore(self):
return tuple(self._revealed_lore)

# *** MEMO FUNCTIONS ***
def get_memo(self):
if self._revealed_lore:
return self

def open_lore(self, wid, ev):
# Open the Hypothesis Widget.
memob, camp = wid.data
memob.active = False
BrowseLoreWidget(self._revealed_lore, camp)()
memob.regen_memo()
memob.active = True

def get_widget(self, memobrowser, camp):
mylabel = widgets.LabelWidget(
memobrowser.dx, memobrowser.dy, memobrowser.w, memobrowser.h, text=str(self),
data=memobrowser, justify=0, font=my_state.medium_font
)
mybutton = widgets.LabelWidget(
-75, 20, 150, 24, text="Review Lore", draw_border=True, justify=0, border=widgets.widget_border_on,
on_click=self.open_lore, data=(memobrowser,camp), parent=mylabel, anchor=frects.ANCHOR_BOTTOM,
font=my_state.big_font
)
mylabel.children.append(mybutton)
return mylabel

def __str__(self):
return self.desc


class LoreBlockRecord:
def __init__(self, qr, new_needed_lore: QuestLore=None, new_locked_lore: QuestLore=None):
Expand Down Expand Up @@ -390,3 +422,50 @@ def add_needed_lore(self, quest, myplot, new_lore):
self._needed_lore.add(new_lore)
else:
raise plots.PlotError("Lore {} will cause lore blockage if claimed by {}.".format(new_lore, myplot))


class BrowseLoreWidget(widgets.ScrollColumnWidget):
def __init__(self, revealed_lore, camp, **kwargs):
up_arrow = widgets.ButtonWidget(-64, -232, 128, 16, sprite=image.Image("sys_updownbuttons.png", 128, 16),
on_frame=0, off_frame=1)
down_arrow = widgets.ButtonWidget(-64, 216, 128, 16, sprite=image.Image("sys_updownbuttons.png", 128, 16),
on_frame=2, off_frame=3)
super().__init__(-250, -200, 500, 400, center_interior=True, padding=16, up_button=up_arrow, down_button=down_arrow, draw_border=True, **kwargs)
self.revealed_lore = revealed_lore
self.camp = camp

self.children.append(up_arrow)
self.children.append(down_arrow)

self.keep_going = True
closebuttonsprite = image.Image('sys_closeicon.png')

self.close_button = widgets.ButtonWidget(
-closebuttonsprite.frame_width // 2, -closebuttonsprite.frame_height // 2, closebuttonsprite.frame_width,
closebuttonsprite.frame_height, closebuttonsprite, 0, on_click=self.close_browser, parent=self,
anchor=frects.ANCHOR_UPPERRIGHT)
self.children.append(self.close_button)

for rl in revealed_lore:
if TEXT_LORE_MEMO in rl.texts:
msg = rl.texts[TEXT_LORE_MEMO]
msg.capitalize()
self.add_interior(widgets.LabelWidget(0,0,self.w,0,msg, font=my_state.medium_font))

def close_browser(self, button=None, ev=None):
self.keep_going = False
my_state.widgets.remove(self)

def __call__(self):
# Run the UI. Clean up after you leave.
my_state.widgets.append(self)
while self.keep_going and not my_state.got_quit:
ev = wait_event()
if ev.type == TIMEREVENT:
my_state.render_and_flip()
elif ev.type == pygame.KEYDOWN:
if ev.key == pygame.K_ESCAPE:
self.keep_going = False

if self in my_state.widgets:
my_state.widgets.remove(self)

0 comments on commit 7bf3e1c

Please sign in to comment.