Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # Unofficial patch for the Game "Another Chance" by Time Wizard Studios
- #
- # For the avoidance of doubt: you, the reader, are hereby authorized to use any
- # part of this patch in any way you choose, without limitation.
- #
- # This explicitly includes Time Wizard Studios, who may take any segments of
- # this patch, up to and including the whole, and integrate them into the base
- # game in any way they see fit, without credit or acknowledgement.
- #
- # (Mostly they probably shouldn't, because Patreon. But some of it isn't what
- # Patreon would care about.)
- # =============================================================================
- # Changelog:
- #
- # Changes made by Gego 2025-08-17:
- # - Fixed B.O.B quest
- # - Fixed "Out on the Waves" quest
- # - Fixed "Medium Thirsty Tree" quest
- #
- #
- # v3.2:
- # - Fixed the accidental removal of everyone _but_ Jo's scenes from the
- # Gallery, introduced in 3.1.
- #
- # v3.1:
- # - Rewrote the larger part of the patch, based in part on a suggestion by
- # ZLZK (of F95).
- #
- # - Removed all use of the name 'Jo' (for now?), which could be confusing
- # if you started with the patch applied:
- # - Removed use of "Jo" by some characters which previous patch-versions
- # explicitly retained.
- # - Adjusted various fragments of text in the quest canonically titled
- # "Jo's Day", with added editorial meddling.
- # - Amended the scene-titles in the Gallery.
- #
- # - Converted the fix for the obscure `maxine_eggs` comment to use the
- # Ren'Py translation mechanism. Lightly tweaked the text for consistency.
- #
- # - Used the translation mechanism to alter a few other strings.
- #
- # v2: Fixed a bug which would crash the game whenever "???" spoke.
- #
- # v1: Original version.
- # =============================================================================
- # Another Chance (henceforth usually "AC") v1.36 has some code that seems
- # to be part of a mechanism whereby the substituted name of a character can
- # be made different depending on who's talking about them.
- #
- # That would be particularly useful for us, if only it were functional.
- # Unfortunately, it's not: none of it's hooked up to anything, nor is there
- # anything shaped like its complement to hook it up to. It can't really be
- # made to work because Ren'Py 7.4.4 (which AC uses) falls _just_ short of
- # being able to hook into the necessary code.
- #
- # Fortunately, we can mostly work around that. Ren'Py doesn't quite provide
- # us with the data we need, but this _is_ Python, so we can monkey-patch in
- # what we want.
- # =============================================================================
- init 1 python hide:
- # Virtually all uses of "Jo" or "[jo]" are in contexts associated with
- # either the main character or Flora. (They're not all speech; many,
- # perhaps most, are narration -- but fortunately, as in most VNs, the
- # narrator is the MC.) Thus, the following line alone would provide 99.9%
- # of the functionality of this patch.
- Character_jo.default_name = u"Mom"
- # As always, the remaining 0.1% requires 99.9% of the work.
- #
- # As of AC v1.36, there are only a handful of places in the entire game
- # where another character (or non-character UI text) names Jo directly.
- #
- # First, there are some UI-only hardcoded uses of "Jo". The hardcoded
- # scene-replay title text is perhaps easiest to fix:
- def fixup_replays():
- import re
- rx = re.compile(r"^Jo's ")
- global replays_by_order
- replays_by_order = [
- (a, rx.sub("Mom's ", title), c, d)
- for (a, title, c, d) in replays_by_order
- ]
- fixup_replays()
- # Also, there's one quest whose title contains it:
- Quest_jo_day.title = "Mom's Day"
- # ... along with some hint-adjacent flavor text in that quest; but that can
- # more easily be taken care of with the translation mechanism. (We do that
- # below.)
- # =========================================================================
- # AC's BaseChar class sets an attribute `game._speaker` to the current
- # speaker of a Say command. We can't quite use that, for reasons that will
- # be explained along the way... but we can set up our own mechanism in
- # parallel instead.
- # Specifically, we store the current speaker in the Ren'Py-store variable
- # `_speaker_true`. (We could just use a variable in this hidden scope; but
- # this way we can easily get at the value from temporary diagnostic code
- # elsewhere, if needed.)
- def get_current_speaker():
- # Note that, as is customary in Ren'Py, this may either be a character
- # object or a bare string.
- return getattr(renpy.store, "_speaker_true", None)
- # Set the current speaker in a particular scope, restoring it on exit.
- #
- # (This is part of the reason we can't use `_speaker`: it never gets unset.
- # If we tried to access it from within a Menu-statement, it would still
- # have its value from the last dialogue-utterance.)
- from contextlib import contextmanager
- @contextmanager
- def speaker_set_to(speaker):
- old_value = get_current_speaker()
- try:
- renpy.store._speaker_true = speaker
- yield None
- finally:
- renpy.store._speaker_true = old_value
- # Monkey-patching utility decorator. Replaces the specified method on a
- # class (or function on an object) with a new method provided. The old
- # method will be provided to the new method as a keyword argument. (In
- # theory we could override `super()`, but it's REALLY not worth the
- # trouble.)
- def replace_method(obj, method):
- def do_wrap(decorated_func):
- old_method = getattr(obj, method)
- def replacement_method(*args, **kwargs):
- return decorated_func(*args, _old_method=old_method, **kwargs)
- replacement_method.__name__ = old_method.__name__
- setattr(obj, method, replacement_method)
- return None
- return do_wrap
- # Wrap BaseChar.__call__ so that the speaker is set...
- @replace_method(BaseChar, "__call__")
- def mark_speaker(self, *args, **kwargs):
- _old_method = kwargs.pop("_old_method")
- with speaker_set_to(self):
- return _old_method(self, *args, **kwargs)
- # ... and replace Character_jo.__str__ entirely, so that it checks the
- # current speaker to decide what to return.
- @replace_method(Character_jo, "__str__")
- def __str__(self, _old_method=None):
- speaker = get_current_speaker()
- # These characters (including None, which represents the MC when
- # speaking in menu text or narration) use the new default name, and
- # need no further processing.
- if speaker in {None, narrator, mc, flora}:
- return self.default_name
- # A previous version of this patch returned u"Jo" as some characters'
- # preferred form of reference:
- #
- # if speaker in {jo, mrsl, nurse, spinach, "???"}:
- # return u"Jo"
- #
- # Unfortunately, this doesn't quite work: if someone installs this
- # patch before they start, they'll be left wondering who the hell this
- # Jo person is when Mrs. L or the nurse eventually namedrop her.
- # Avoiding this would require explicitly injecting "Jo" somewhere where
- # the player will be guaranteed to see it, and I haven't (yet?) found a
- # non-awkward place to do that.
- #
- # At present, the other characters that refer to "[jo]" only do so when
- # addressing the protagonist, so the default below still works.
- # Note that this will fail if "[jo]" ever heads a sentence. No attempt
- # is made to detect that: it must be handled on a case-by-case basis.
- # (TWS could handle it by using "[jo!c]" instead; but there's only one
- # reason to do that, and they probably shouldn't.)
- return u'your mom'
- # You'd think that would cover it. Unfortunately, that covers too much.
- #
- # It may seem intuitive that a Say-statement's evaluation would happen as
- # part of ordinary screen layout. This is the opposite of the truth: screen
- # layout is performed ** as part of Say-statement evaluation **.
- #
- # If `str(jo)` is evaluated during screen layout, its current value will
- # depend on which character, if any, happens to be speaking at the time. If
- # the MC is speaking with another character while the result of that
- # evaluation is on-screen, it'll flip back and forth between "Mom" and
- # "your mom" as the characters alternate utterances.
- #
- # This is not hypothetical. In v1.36, it happens in the "phone_app_call"
- # screen when calling Jo. (Coincidentally, this screen is also the other
- # reason we can't use `_speaker`: it's it's accessed during screen layout
- # to position the MC's dialogue, while we need something that's `None` in
- # that same phase!)
- #
- # There isn't any scope that we can reasonably intercept in which
- # dialogue-text-substitution happens but screen-evaluation doesn't: the
- # former happens in `ADVCharacter.__call__`, which also invokes the latter.
- # However, there _is_ an interceptable scope in which screen-evaluation
- # happens, but text-substitution does not.
- #
- # So we just intercept that too. That's probably as sharp as we can get.
- @replace_method(ADVCharacter, "do_display")
- def mark_speaker(self, *args, **kwargs):
- _old_method = kwargs.pop("_old_method")
- with speaker_set_to(None):
- return _old_method(self, *args, **kwargs)
- # That's not great, because it involves monkey-patching the framework
- # instead of just the game. It should still work up to Ren'Py 8.1.3 (and
- # it's not likely AC will ever update its Ren'Py version anyway), but it
- # still leaves a foul taste in my mouth.
- #
- # If we knew for sure that "phone_app_call" was the only place where that
- # could happen, we could instead just monkey-patch that one screen...
- def fixup_phone():
- __phonescreen = renpy.display.screen.get_screen_variant("phone_app_call",None)
- __phonescreen.function.children[1].children[0].positional[0] = \
- u'contact.default_name'
- # fixup_phone()
- # ...but this is fragile in the face of minor stylistic changes to the
- # screen(s) in question, won't cover any uses in new screens while the game
- # is in development, and _still_ involves mucking around in Ren'Py
- # internals.
- # With the generic part out of the way, we turn to local and specific
- # adjustments. The Ren'Py translation mechanism turns out to be perfect for
- # this, so we define a fake language we're translating into.
- define config.language = "ipatch"
- # However: beyond this point necessarily lie artistic interpretations and,
- # therefore, decisions. Abandon all pretense to objectivity, ye who enter here.
- translate ipatch quest_flora_bonsai_bed_16ea41b6:
- # flora bed flirty "Being your roommate has taught me everything I need to know about purifying unholy taint."
- flora bed flirty "Being your sister has taught me everything I need to know about purifying unholy taint."
- translate ipatch quest_flora_squid_trail_flora_e6f80e4d:
- # mc "Maybe it slipped into the principal's office?"
- mc "Maybe it slipped into [jo]'s office?"
- translate ipatch quest_jo_day_amends_2bdfe697:
- # mc "Because, let's be honest, you're the mother of every student in Newfall High."
- mc "Well, give or take half a year. But better late than never!"
- "Later than you know, [jo]. Later than you know."
- # This could be more specific; it's been left vague for now to avoid the
- # possibility of contradictions with new material.
- translate ipatch quest_jo_day_picnic_3e4a8f94:
- # "She kept trying to make out with me and called me by her husband's name..."
- "She kept trying to make out with me and calling me the wrong name..."
- translate ipatch strings:
- # Guides and flavor text for the quest "jo_day".
- old "Today is the big day. Jo's day!"
- new "Today is the big day. Mom's day!"
- old "Tomorrow is the big day. Jo's day!"
- new "Tomorrow is the big day. Mom's day!"
- old "Jo's day or my day? Our day!"
- new "Mom's day or my day? Our day!"
- # fix B.O.B quest - Gego
- old "Your landlady and roommate will swallow my wood chips!"
- new "Your mother and sister will swallow my wood chips!"
- old "I'll spank your roommate with my paddle tail and make her gnaw my log!{space=-85}"
- new "I'll spank your sister with my paddle tail and make her gnaw my log!{space=-85}"
- # Fix "Out on the Waves" quest - Gego
- old "Call your annoying roommate."
- new "Call your annoying sister."
- # Fix "Medium Thirsty Tree" quest - Gego
- old "Converse with your roommate about, err... roommate stuff."
- new "Converse with your sister about, err... sibling stuff."
- # This bit is incredibly niche; it's intended to fix a very minor bug
- # introduced into the `quest_maxine_eggs_bathroom_encounter` conversation...
- # and also to clean up the associated phrasing a little bit, because several of
- # the relevant characters probably don't have a locker.
- translate ipatch quest_maxine_eggs_bathroom_encounter_fa04a789:
- # lindsey eyeroll "Nope. Have you checked [not_best_girl]'s locker?"
- $ not_best_girl = max([jo, flora, mrsl, isabelle, kate, jacklyn, nurse, maxine], key=lambda c: c.love)
- if not_best_girl is jo:
- $ _fa04a789_storage = "office"
- elif not_best_girl is nurse:
- $ _fa04a789_storage = "office"
- $ not_best_girl = "the nurse"
- elif not_best_girl is mrsl:
- $ _fa04a789_storage = "desk"
- else:
- $ _fa04a789_storage = "locker"
- lindsey eyeroll "Nope. Have you checked [not_best_girl]'s [_fa04a789_storage]?"
Advertisement
Add Comment
Please, Sign In to add comment