From cceec862afb9cc52c898c0b0d659edd5394ebb13 Mon Sep 17 00:00:00 2001 From: LoafyLemon Date: Fri, 23 Jun 2023 19:07:37 +0100 Subject: [PATCH] Expand EventQueue and Event classes * Added event callbacks support * Added support for in-game favour-like behaviour * Added debug functions * Added extensive event parameters * Added a bunch of new methods * Added modding support * Added completion tracking * Added menu handlers * Added global vars * ... more --- .../hermione/events/items/butt_plugs.rpy | 6 +- .../hermione/events/items/vibrators.rpy | 2 +- game/scripts/events/queue.rpy | 224 ++++++++++++++++-- game/scripts/utility/common_functions.rpy | 2 +- 4 files changed, 209 insertions(+), 25 deletions(-) diff --git a/game/scripts/characters/hermione/events/items/butt_plugs.rpy b/game/scripts/characters/hermione/events/items/butt_plugs.rpy index fb46f58b..1244c7b8 100644 --- a/game/scripts/characters/hermione/events/items/butt_plugs.rpy +++ b/game/scripts/characters/hermione/events/items/butt_plugs.rpy @@ -1,6 +1,6 @@ -default ev_her_small_plug = Event(id="her_small_plug", daytime=False, label="hg_butt_plugs_small_return") -default ev_her_medium_plug = Event(id="her_medium_plug", daytime=False, label="hg_butt_plugs_medium_return") -default ev_her_large_plug = Event(id="her_large_plug", daytime=False, label="hg_butt_plugs_large_return") +default ev_her_small_plug = Event(id="her_small_plug", label="hg_butt_plugs_small_return", daytime=False) +default ev_her_medium_plug = Event(id="her_medium_plug", label="hg_butt_plugs_medium_return", daytime=False) +default ev_her_large_plug = Event(id="her_large_plug", label="hg_butt_plugs_large_return", daytime=False) label hg_butt_plugs: diff --git a/game/scripts/characters/hermione/events/items/vibrators.rpy b/game/scripts/characters/hermione/events/items/vibrators.rpy index 1c2e4612..81fe35c9 100644 --- a/game/scripts/characters/hermione/events/items/vibrators.rpy +++ b/game/scripts/characters/hermione/events/items/vibrators.rpy @@ -1,5 +1,5 @@ -default ev_her_vibrators_public_return = Event(id="ev_her_vibrators_public_return", daytime=False, label="hg_vibrators_public_return") +default ev_her_vibrators_public_return = Event(id="her_vibrators_public_return", label="hg_vibrators_public_return", daytime=False) label hg_vibrators: diff --git a/game/scripts/events/queue.rpy b/game/scripts/events/queue.rpy index 44809e4f..12072452 100644 --- a/game/scripts/events/queue.rpy +++ b/game/scripts/events/queue.rpy @@ -1,13 +1,15 @@ +default event_callbacks = [] + init -1 python: + class EventQueue(object): - def __init__(self): + def __init__(self, id=None): + self.id = id + self.queue = [] self.freeze = False - def get_events(self, raw=False): - return self.queue if raw else [x for x in self.queue if x.wait < 1] - def freeze(self): self.freeze = True @@ -26,32 +28,96 @@ init -1 python: for i in self.queue: i.wait -= 1 - def start(self): - queue = self.queue - queue = [x for x in queue if (x.wait <= 0) and (x.daytime == game.daytime)] + def list_applicable(self): + queue = [ev for ev in self.queue if (ev.wait <= 0) and ( ev.daytime is None or (ev.daytime == game.daytime) ) and not ev.disabled and ev.requirements_met() ] queue.sort(key=lambda x: x.priority) + return queue + + def start(self, allow_repeat=True): + global _ev_queue_completed, _ev_queue_any_completed + + queue = self.list_applicable() + + _ev_queue_completed = all(ev.completed for ev in queue) + _ev_queue_any_completed = any(ev.completed for ev in queue) + for ev in queue: - if not ev.requirements_met(): + if not ev.repeat and ev.completed: continue - ev.start() + if config.developer: + print(f"Starting event id '{ev.id}' ... ") - if ev.label: - break + return ev.start() + + if queue and allow_repeat: + return renpy.random.choice(queue).start() def is_in_queue(self, ev): if isinstance(ev, str): return any(i.id == ev for i in self.queue) return ev in self.queue - class Event(object): - """ - Queue is universal for all instanced objects. - """ + # def menu(self, title, **kwargs): + # hints = self.menu_hints() + # title += hints - def __init__(self, id, wait=0, priority=5, daytime=True, req=None, label=None, func=None): - self.queued = False + # return renpy.display_menu( [ (title, self) ], **kwargs) + + def menu_hints(self): + queue = self.list_applicable() + total_applicable = len(queue) + total_events = len(self.queue) + completed = 0 + hints = [] + + for ev in queue: + if ev.completed: + hints.append("v") + completed += 1 + elif ev.completed_failed: + hints.append("x") + completed += 1 + else: + hints.append("o") + + s = "(" + "".join(hints) + f") {completed}/{total_applicable}" + + if config.developer: + s += f"/{total_events}" + + return s + + def _debug(self): + queue = self.queue + queue.sort(key=lambda x: x.priority) + + for ev in queue: + s = f"ID:{ev.id} QUE:{ev.queued} STA:{ev.started} COM:{ev.completed} COMF:{ev.completed_failed} DIS:{ev.disabled}" + + print(s) + + def _debug_menu(self): + queue = self.queue + queue.sort(key=lambda x: x.priority) + + menu = [] + + for ev in queue: + req = f"{{color=#00ff00}}{ev.req}{{/color}}" if ev.requirements_met() else f"{{color=#ff0000}}{ev.req}{{/color}}" + + s = f"{{size=12}}id:{ev.id} label:{ev.label}{{/size}}{{size=8}}\npriority:{ev.priority} Daytime:{ev.daytime} Req:{req} AutoENQ:{ev.autoenqueue} AutoDEQ:{ev.autodequeue}{{/size}}" + l = ev.label + + menu.append( (s, l) ) + + renpy.display_menu(menu) + + class Event(object): + _queue = None + + def __init__(self, id, wait=0, priority=5, daytime=None, req=None, label=None, func=None, queue="eventqueue", autoenqueue=False, autodequeue=True, repeat=True, fail_suffixes=("_fail", "too_much", "too_much_public"), ignore_labels=[]): self.id = id self.wait = wait self.priority = priority @@ -59,11 +125,45 @@ init -1 python: self.req = req self.label = label self.func = func - self.queue = eventqueue.queue + self.queue = queue + self.autoenqueue = autoenqueue + self.autodequeue = autodequeue + self.repeat = repeat + self.fail_suffixes = tuple(fail_suffixes) + self.ignore_labels = ignore_labels + + self.queued = False + self.started = False + self.completed = False + self.completed_failed = False + self.disabled = False if not renpy.has_label(self.label): raise Exception("Supplied label does not exist.") + if autoenqueue: + self.enqueue() + + def disable(self): + self.disabled = True + + def enable(self): + self.disabled = False + + @property + def queue(self): + return getattr(store, self._queue).queue if self._queue else None + + @queue.setter + def queue(self, name): + self._queue = name + + if name is not None and not hasattr(store, name): + if config.developer: + print(f"Creating EventQueue object named '{name}' ...") + + setattr(store, name, EventQueue(name)) + def enqueue(self): self.queued = True @@ -76,15 +176,99 @@ init -1 python: return True def start(self): - if self in self.queue: + global _ev_completed, _ev_completed_failed + + self.started = True + + _ev_completed = self.completed + _ev_completed_failed = self.completed_failed + + if self.autodequeue and self in self.queue: self.queue.remove(self) + self.queued = False + + ## additional requirements check + + if not self._track_completion in event_callbacks: + event_callbacks.append(self._track_completion) if self.func: self.func() if self.label: - renpy.jump(self.label) + return renpy.jump(self.label) + + def _track_completion(self, label, abnormal): + if renpy.is_init_phase(): + return + + if label == self.label: + # Ignore jumps to self. + return + + if label in self.ignore_labels: + # Postpone completing the event until end label returns + return + + # if _last_label_call == label: + # # Ignore calls. + # return + + # if abnormal: + # return + + if renpy.game.context().return_stack: + # If return stack exists, ignore, because we're probably in a call label. + return + + if label.endswith(self.fail_suffixes): + self.completed_failed = True + else: + self.completed = True + + if self._track_completion in event_callbacks: + event_callbacks.remove(self._track_completion) + + # def catch_label_call(label, args, kwargs): + # if config.developer: + # print(f"Called '{label}' with ARGS: {args} KWARGS: {kwargs}") + + # global _last_label_call + # _last_label_call = label + + def execute_event_callbacks(label, abnormal): + if renpy.is_init_phase() or not hasattr(store, "event_callbacks"): + return + + for callback in event_callbacks: callback(label, abnormal) + + def initialize_callbacks(): + if renpy.in_rollback(): + return + + # We need to add these after defaults are finished. + renpy.config.label_callbacks.append(execute_event_callbacks) + # renpy.config.call_callbacks.append(catch_label_call) + + def show_events_menu(queues, **kwargs): + l = [] + + for queue in queues: + if isinstance(queue, str): + queue = getattr(store, queue) + + if not queue.list_applicable(): + continue + + title = f"-{queue.id}- {queue.menu_hints()}" + + l.append( (title, queue) ) + + return renpy.display_menu(l, **kwargs) + + config.after_default_callbacks.append(initialize_callbacks) init offset = -5 default eventqueue = EventQueue() +# default _last_label_call = None \ No newline at end of file diff --git a/game/scripts/utility/common_functions.rpy b/game/scripts/utility/common_functions.rpy index 0243d286..20eefb75 100644 --- a/game/scripts/utility/common_functions.rpy +++ b/game/scripts/utility/common_functions.rpy @@ -224,4 +224,4 @@ init python early: return len(self._callable()) def execute_callbacks(callbacks): - [callback() for callback in callbacks] \ No newline at end of file + for callback in callbacks: callback() \ No newline at end of file