From 04894a490663acd1a20214993c5ff0353a932e31 Mon Sep 17 00:00:00 2001 From: Samuel Bouchet Date: Tue, 10 Mar 2026 18:24:01 +0100 Subject: [PATCH] Initial project setup: Open The Box CLI game - .NET 10 console app with Spectre.Console and Loreline integration - Black Box Sim architecture (simulation separated from presentation) - Progressive CLI rendering (9 phases from basic to full layout) - 25+ box definitions with weighted loot tables - 100+ item definitions (meta, cosmetics, materials, adventure tokens) - 9 Loreline adventures (Space, Medieval, Pirate, etc.) - Bilingual content (EN/FR) - Save/load system - Game Design Document --- .gitignore | 43 + OpenTheBox.slnx | 5 + content/adventures/contemporary/intro.lor | 241 +++++ content/adventures/cosmic/intro.lor | 242 +++++ content/adventures/darkfantasy/intro.lor | 257 +++++ content/adventures/medieval/intro.lor | 240 +++++ content/adventures/microscopic/intro.lor | 231 +++++ content/adventures/pirate/intro.lor | 277 ++++++ content/adventures/prehistoric/intro.lor | 218 ++++ content/adventures/sentimental/intro.lor | 197 ++++ content/adventures/space/intro.fr.lor | 299 ++++++ content/adventures/space/intro.lor | 191 ++++ content/data/boxes.json | 558 +++++++++++ content/data/interactions.json | 52 + content/data/items.json | 153 +++ content/strings/en.json | 339 +++++++ content/strings/fr.json | 339 +++++++ docs/GDD.md | 932 ++++++++++++++++++ lib/Loreline.deps.json | 24 + lib/Loreline.dll | Bin 0 -> 654848 bytes lib/Loreline.xml | 495 ++++++++++ src/OpenTheBox/Adventures/AdventureEngine.cs | 258 +++++ src/OpenTheBox/Core/Boxes/BoxDefinition.cs | 16 + src/OpenTheBox/Core/Boxes/LootCondition.cs | 13 + src/OpenTheBox/Core/Boxes/LootEntry.cs | 11 + src/OpenTheBox/Core/Boxes/LootTable.cs | 10 + .../Core/Characters/PlayerAppearance.cs | 17 + src/OpenTheBox/Core/Enums/AdventureTheme.cs | 36 + src/OpenTheBox/Core/Enums/ArmStyle.cs | 29 + src/OpenTheBox/Core/Enums/BodyStyle.cs | 26 + src/OpenTheBox/Core/Enums/CosmeticSlot.cs | 23 + src/OpenTheBox/Core/Enums/EnvironmentType.cs | 17 + src/OpenTheBox/Core/Enums/EyeStyle.cs | 41 + src/OpenTheBox/Core/Enums/FontStyle.cs | 27 + src/OpenTheBox/Core/Enums/HairStyle.cs | 33 + .../Core/Enums/InteractionResultType.cs | 29 + src/OpenTheBox/Core/Enums/ItemCategory.cs | 57 ++ src/OpenTheBox/Core/Enums/ItemRarity.cs | 26 + src/OpenTheBox/Core/Enums/LegStyle.cs | 32 + src/OpenTheBox/Core/Enums/Locale.cs | 15 + .../Core/Enums/LootConditionType.cs | 36 + src/OpenTheBox/Core/Enums/MaterialForm.cs | 36 + src/OpenTheBox/Core/Enums/MaterialType.cs | 30 + src/OpenTheBox/Core/Enums/Mood.cs | 27 + src/OpenTheBox/Core/Enums/ResourceType.cs | 33 + src/OpenTheBox/Core/Enums/StatType.cs | 26 + src/OpenTheBox/Core/Enums/TextColor.cs | 39 + src/OpenTheBox/Core/Enums/TintColor.cs | 45 + src/OpenTheBox/Core/Enums/UIFeature.cs | 45 + src/OpenTheBox/Core/Enums/WorkstationType.cs | 105 ++ src/OpenTheBox/Core/GameState.cs | 105 ++ .../Core/Interactions/InteractionResult.cs | 11 + .../Core/Interactions/InteractionRule.cs | 18 + src/OpenTheBox/Core/Items/ItemDefinition.cs | 24 + src/OpenTheBox/Core/Items/ItemInstance.cs | 18 + src/OpenTheBox/Data/ContentRegistry.cs | 83 ++ .../Localization/LocalizationManager.cs | 80 ++ src/OpenTheBox/OpenTheBox.csproj | 28 + src/OpenTheBox/Persistence/SaveData.cs | 28 + src/OpenTheBox/Persistence/SaveManager.cs | 127 +++ src/OpenTheBox/Program.cs | 422 ++++++++ src/OpenTheBox/Rendering/BasicRenderer.cs | 124 +++ src/OpenTheBox/Rendering/IRenderer.cs | 75 ++ src/OpenTheBox/Rendering/Panels/ChatPanel.cs | 61 ++ .../Rendering/Panels/InventoryPanel.cs | 65 ++ .../Rendering/Panels/PortraitPanel.cs | 134 +++ .../Rendering/Panels/ResourcePanel.cs | 60 ++ src/OpenTheBox/Rendering/Panels/StatsPanel.cs | 54 + src/OpenTheBox/Rendering/RenderContext.cs | 53 + src/OpenTheBox/Rendering/RendererFactory.cs | 37 + src/OpenTheBox/Rendering/SpectreRenderer.cs | 411 ++++++++ .../Simulation/Actions/GameAction.cs | 52 + src/OpenTheBox/Simulation/BoxEngine.cs | 154 +++ src/OpenTheBox/Simulation/Events/GameEvent.cs | 72 ++ src/OpenTheBox/Simulation/GameSimulation.cs | 221 +++++ .../Simulation/InteractionEngine.cs | 139 +++ src/OpenTheBox/Simulation/MetaEngine.cs | 61 ++ src/OpenTheBox/Simulation/ResourceEngine.cs | 51 + src/OpenTheBox/Simulation/WeightedRandom.cs | 46 + 79 files changed, 9285 insertions(+) create mode 100644 .gitignore create mode 100644 OpenTheBox.slnx create mode 100644 content/adventures/contemporary/intro.lor create mode 100644 content/adventures/cosmic/intro.lor create mode 100644 content/adventures/darkfantasy/intro.lor create mode 100644 content/adventures/medieval/intro.lor create mode 100644 content/adventures/microscopic/intro.lor create mode 100644 content/adventures/pirate/intro.lor create mode 100644 content/adventures/prehistoric/intro.lor create mode 100644 content/adventures/sentimental/intro.lor create mode 100644 content/adventures/space/intro.fr.lor create mode 100644 content/adventures/space/intro.lor create mode 100644 content/data/boxes.json create mode 100644 content/data/interactions.json create mode 100644 content/data/items.json create mode 100644 content/strings/en.json create mode 100644 content/strings/fr.json create mode 100644 docs/GDD.md create mode 100644 lib/Loreline.deps.json create mode 100644 lib/Loreline.dll create mode 100644 lib/Loreline.xml create mode 100644 src/OpenTheBox/Adventures/AdventureEngine.cs create mode 100644 src/OpenTheBox/Core/Boxes/BoxDefinition.cs create mode 100644 src/OpenTheBox/Core/Boxes/LootCondition.cs create mode 100644 src/OpenTheBox/Core/Boxes/LootEntry.cs create mode 100644 src/OpenTheBox/Core/Boxes/LootTable.cs create mode 100644 src/OpenTheBox/Core/Characters/PlayerAppearance.cs create mode 100644 src/OpenTheBox/Core/Enums/AdventureTheme.cs create mode 100644 src/OpenTheBox/Core/Enums/ArmStyle.cs create mode 100644 src/OpenTheBox/Core/Enums/BodyStyle.cs create mode 100644 src/OpenTheBox/Core/Enums/CosmeticSlot.cs create mode 100644 src/OpenTheBox/Core/Enums/EnvironmentType.cs create mode 100644 src/OpenTheBox/Core/Enums/EyeStyle.cs create mode 100644 src/OpenTheBox/Core/Enums/FontStyle.cs create mode 100644 src/OpenTheBox/Core/Enums/HairStyle.cs create mode 100644 src/OpenTheBox/Core/Enums/InteractionResultType.cs create mode 100644 src/OpenTheBox/Core/Enums/ItemCategory.cs create mode 100644 src/OpenTheBox/Core/Enums/ItemRarity.cs create mode 100644 src/OpenTheBox/Core/Enums/LegStyle.cs create mode 100644 src/OpenTheBox/Core/Enums/Locale.cs create mode 100644 src/OpenTheBox/Core/Enums/LootConditionType.cs create mode 100644 src/OpenTheBox/Core/Enums/MaterialForm.cs create mode 100644 src/OpenTheBox/Core/Enums/MaterialType.cs create mode 100644 src/OpenTheBox/Core/Enums/Mood.cs create mode 100644 src/OpenTheBox/Core/Enums/ResourceType.cs create mode 100644 src/OpenTheBox/Core/Enums/StatType.cs create mode 100644 src/OpenTheBox/Core/Enums/TextColor.cs create mode 100644 src/OpenTheBox/Core/Enums/TintColor.cs create mode 100644 src/OpenTheBox/Core/Enums/UIFeature.cs create mode 100644 src/OpenTheBox/Core/Enums/WorkstationType.cs create mode 100644 src/OpenTheBox/Core/GameState.cs create mode 100644 src/OpenTheBox/Core/Interactions/InteractionResult.cs create mode 100644 src/OpenTheBox/Core/Interactions/InteractionRule.cs create mode 100644 src/OpenTheBox/Core/Items/ItemDefinition.cs create mode 100644 src/OpenTheBox/Core/Items/ItemInstance.cs create mode 100644 src/OpenTheBox/Data/ContentRegistry.cs create mode 100644 src/OpenTheBox/Localization/LocalizationManager.cs create mode 100644 src/OpenTheBox/OpenTheBox.csproj create mode 100644 src/OpenTheBox/Persistence/SaveData.cs create mode 100644 src/OpenTheBox/Persistence/SaveManager.cs create mode 100644 src/OpenTheBox/Program.cs create mode 100644 src/OpenTheBox/Rendering/BasicRenderer.cs create mode 100644 src/OpenTheBox/Rendering/IRenderer.cs create mode 100644 src/OpenTheBox/Rendering/Panels/ChatPanel.cs create mode 100644 src/OpenTheBox/Rendering/Panels/InventoryPanel.cs create mode 100644 src/OpenTheBox/Rendering/Panels/PortraitPanel.cs create mode 100644 src/OpenTheBox/Rendering/Panels/ResourcePanel.cs create mode 100644 src/OpenTheBox/Rendering/Panels/StatsPanel.cs create mode 100644 src/OpenTheBox/Rendering/RenderContext.cs create mode 100644 src/OpenTheBox/Rendering/RendererFactory.cs create mode 100644 src/OpenTheBox/Rendering/SpectreRenderer.cs create mode 100644 src/OpenTheBox/Simulation/Actions/GameAction.cs create mode 100644 src/OpenTheBox/Simulation/BoxEngine.cs create mode 100644 src/OpenTheBox/Simulation/Events/GameEvent.cs create mode 100644 src/OpenTheBox/Simulation/GameSimulation.cs create mode 100644 src/OpenTheBox/Simulation/InteractionEngine.cs create mode 100644 src/OpenTheBox/Simulation/MetaEngine.cs create mode 100644 src/OpenTheBox/Simulation/ResourceEngine.cs create mode 100644 src/OpenTheBox/Simulation/WeightedRandom.cs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a336598 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +## .NET +bin/ +obj/ +*.user +*.suo +*.userosscache +*.sln.docstates +.vs/ +*.dll +*.pdb +*.exe + +## NuGet +**/[Pp]ackages/* +!**/[Pp]ackages/build/ +*.nupkg +**/[Pp]roject.lock.json +project.assets.json + +## Keep Loreline DLL +!lib/Loreline.dll + +## Build results +[Dd]ebug/ +[Rr]elease/ +x64/ +x86/ + +## Saves +saves/ + +## IDE +.idea/ +*.swp +*~ +.vscode/ + +## OS +Thumbs.db +.DS_Store + +## Claude +.claude/ diff --git a/OpenTheBox.slnx b/OpenTheBox.slnx new file mode 100644 index 0000000..b2df31d --- /dev/null +++ b/OpenTheBox.slnx @@ -0,0 +1,5 @@ + + + + + diff --git a/content/adventures/contemporary/intro.lor b/content/adventures/contemporary/intro.lor new file mode 100644 index 0000000..27a657f --- /dev/null +++ b/content/adventures/contemporary/intro.lor @@ -0,0 +1,241 @@ +// Contemporary Adventure - Open The Box +// Theme: Corporate office meets mysterious box absurdity + +character sandrea + name: Sandrea + role: Office Worker + +character samuel + name: Samuel + role: IT Guy + +state + emails: 0 + boxPower: 0 + hrInvolved: false + unionFormed: false + +beat Intro + Monday morning. Fluorescent lights. The faint smell of burned coffee and crushed dreams. #intro-monday + You arrive at Boxington & Associates Ltd. (Motto: "Thinking Outside The Box Since Never"). #intro-arrive + sandrea: Hey, have you seen the break room? Something... appeared over the weekend. #sandrea-seen + sandrea: And by "something," I mean a box. And by "appeared," I mean nobody will claim responsibility. #sandrea-appeared + + choice + Go check the break room #opt-check + -> BreakRoom + Check your email first #opt-email + emails += 1 + -> CheckEmail + Pretend you didn't hear anything #opt-pretend + -> Pretend + +beat CheckEmail + You open your inbox. 47 unread emails. 46 of them are about the box. #email-open + Subject lines include: "RE: RE: RE: FW: THE BOX", "Box Situation Update #17", and "Please stop replying all about the box." #email-subjects + The last email is from HR: "Mandatory Box Awareness Training - Tuesday 2pm. Attendance is not optional." #email-hr + emails += 46 + hrInvolved = true + sandrea: HR sent a training about the box. It hasn't even been 24 hours. #sandrea-hr + sandrea: That's a new record. Usually it takes them two weeks to acknowledge anything exists. #sandrea-record + -> BreakRoom + +beat Pretend + You put on your headphones and stare at a spreadsheet. #pretend-headphones + The spreadsheet stares back. One cell reads "THE BOX KNOWS." #pretend-cell + You did not type that. #pretend-didnt + sandrea: You can't ignore it. Kevin tried. He's been transferred to the Box Department. #sandrea-kevin + sandrea: We don't have a Box Department. Or we didn't. Until today. #sandrea-department + -> BreakRoom + +beat BreakRoom + You enter the break room. There it is. A plain cardboard box, sitting on the counter next to the microwave. #breakroom-enter + It's perfectly ordinary, except for the fact that it's humming. And glowing faintly. And someone put googly eyes on it. #breakroom-humming + sandrea: The googly eyes were Dave from accounting. He thought it would make it less menacing. #sandrea-dave + sandrea: It did not make it less menacing. #sandrea-menacing + samuel: Hey, IT here. I've been asked to "scan" the box. With what, I have no idea. #samuel-scan + samuel: My job description says "computer stuff." This is not computer stuff. #samuel-job + + choice + Help Samuel scan the box #opt-help-scan + -> ScanBox + Try to open the box #opt-try-open + -> TryOpen + Ask the box what it wants #opt-ask-box + boxPower += 10 + -> AskBox + +beat ScanBox + Samuel holds up his phone's flashlight to the box. #scan-phone + samuel: Scan complete. Results: it's a box. #samuel-results + samuel: You know, I have a computer science degree. Two, actually. And here I am, scanning a box with a phone flashlight. #samuel-degree + sandrea: Did you try turning the box off and on again? #sandrea-turnitoff + samuel: That's not-- you know what, let me try. #samuel-try + He picks up the box, turns it upside down, and puts it back. The humming gets louder. #scan-upside + samuel: I've made it angry. Great. Another thing that's angry at IT. #samuel-angry + boxPower += 5 + -> BoxEscalation + +beat TryOpen + You reach for the box's flaps. #open-reach + sandrea: Wait! What if it's someone's lunch? #sandrea-lunch + sandrea: Remember the Great Tupperware Incident of 2019? Karen still hasn't forgiven us. #sandrea-karen + You hesitate, then open it anyway. Inside the box, there's... another box. #open-another + Inside that box: a Post-it note that reads "Nice try. - The Box" #open-postit + boxPower += 10 + -> BoxEscalation + +beat AskBox + You lean toward the box and whisper: "What do you want?" #ask-whisper + The box hums at a slightly different frequency. Your phone autocorrects a text to "BOX IS LIFE." #ask-phone + sandrea: Did the box just hack your phone? #sandrea-hack + samuel: Boxes don't hack phones! #samuel-hack + samuel: ... Do they? I didn't study box-hacking in college. I'm starting to think my education had gaps. #samuel-gaps + The break room lights flicker. The microwave turns on by itself and heats nothing for exactly thirty seconds. #ask-flicker + -> BoxEscalation + +beat BoxEscalation + An email arrives. From the box. It has its own email address: box@boxington-associates.com. #escalation-email + emails += 1 + The email reads: "Dear colleagues, I would like to schedule a meeting to discuss my role in the organization. Please bring snacks. Preferably cardboard-based." #escalation-meeting + sandrea: The box wants a meeting. #sandrea-meeting + samuel: The box has better email etiquette than half the sales team. #samuel-etiquette + + choice + Schedule the meeting #opt-schedule + -> BoxMeeting + Forward the email to HR #opt-forward-hr + hrInvolved = true + emails += 5 + -> HRResponse + Reply all with "Please remove me from this thread" #opt-reply-all + emails += 200 + -> ReplyAllChaos + +beat ReplyAllChaos + You hit Reply All. The office erupts. #reply-chaos + 47 people reply all with "Please stop replying all." Each reply triggers more replies. #reply-trigger + emails += 200 + samuel: The email server is at 98% capacity. All from box-related reply-alls. #samuel-server + sandrea: Dave from accounting replied with a GIF of a box. It's 47 megabytes. #sandrea-gif + sandrea: The email server has crashed. The box's email still works though. Somehow. #sandrea-crashed + The box sends one final email: "I rest my case. You need me." #reply-rest + boxPower += 20 + -> BoxUnion + +beat HRResponse + HR responds within minutes. A new record. Their email is seventeen paragraphs long. #hr-response + Key excerpts: "Per company policy section 7.4.2, unidentified boxes must complete onboarding." #hr-onboarding + "Please ensure the box has filled out Form B-0X (yes, really) and attended the safety briefing." #hr-form + sandrea: HR wants to onboard the box. They want to give it an employee badge. #sandrea-badge + samuel: I've been here three years and still don't have an employee badge. #samuel-badge + samuel: But sure. Give it to the box. The sentient, humming, email-sending box. #samuel-sentient + -> BoxMeeting + +beat BoxMeeting + The meeting room fills up. Everyone is there. The box sits in a chair. Someone gave it a name tag: "THE BOX - Role: TBD." #meeting-room + boxPower += 10 + sandrea: So... the box has prepared a presentation. #sandrea-presentation + The projector turns on. Slide 1: "Why This Office Needs Me: A 47-Slide Deck by THE BOX." #meeting-slide + samuel: It has better slides than my last quarterly review. #samuel-slides + Slide 7 shows a pie chart. 100% of the pie is labeled "BOX." #meeting-pie + Slide 23 is just the word "OPEN" in 72-point font, pulsing. #meeting-open + Slide 47: "In conclusion: open me. Or don't. I'll still be here Monday." #meeting-conclusion + + choice + Vote to make the box a full employee #opt-employee + unionFormed = true + -> BoxEmployee + Vote to put the box in storage #opt-storage + -> BoxStorage + Open the box during the meeting #opt-open-meeting + -> OpenDuringMeeting + +beat BoxStorage + You vote to put the box in storage. The box's hum drops to a lower frequency. It sounds... hurt. #storage-vote + samuel: I think we hurt the box's feelings. #samuel-hurt + sandrea: Does the box have feelings? #sandrea-feelings + The lights flicker. Every computer in the office displays "I HAVE FEELINGS" in Comic Sans. #storage-feelings + boxPower += 30 + sandrea: Apparently yes. And it has opinions about fonts. #sandrea-fonts + The box returns to the break room on its own. Nobody sees it move. It's just... there again. #storage-returns + -> BoxUnion + +beat OpenDuringMeeting + You stand up and open the box mid-presentation. Gasp from the audience. #open-gasp + Inside the box: a smaller box. Inside that: a USB drive. #open-usb + samuel: A USB drive? Now we're in MY territory! #samuel-usb + He plugs it in. The screen displays: "Company Performance Could Be 340% Better If You Opened More Boxes - A Study by THE BOX." #open-study + samuel: The data is... actually compelling? The methodology is sound? #samuel-data + sandrea: We're being out-performed by a box. On a Monday. #sandrea-outperformed + boxPower += 15 + -> BoxUnion + +beat BoxEmployee + The box is officially hired. Employee #BOX-001. #employee-hired + It gets a desk, a computer it doesn't use, and a parking spot it definitely doesn't need. #employee-desk + sandrea: The box got a parking spot before I did. I've been on the waitlist for two years. #sandrea-parking + samuel: The box's employee portal says its skills are "containing things" and "being opened." #samuel-skills + samuel: That's more than my resume had when I started here. #samuel-resume + boxPower += 20 + -> BoxUnion + +beat BoxUnion + Weeks pass. The box has gained a following. Other boxes have appeared. #union-weeks + The supply closet, the printer paper boxes, the shipping containers -- they're all... organizing. #union-organizing + sandrea: The boxes are forming a union. #sandrea-union + samuel: Can boxes unionize? Is that legal? #samuel-legal + unionFormed = true + A memo slides under the door. It reads: "The United Box Workers of Boxington & Associates demand: 1) Better storage conditions. 2) No more being thrown away on Fridays. 3) One seat on the board of directors." #union-memo + emails += 30 + + choice + Support the box union #opt-support + -> SupportUnion + Oppose the box union #opt-oppose + -> OpposeUnion + Negotiate with the box union #opt-negotiate + -> NegotiateUnion + +beat SupportUnion + You sign the box union's petition. It's the first petition you've ever signed that was also a box. #support-petition + sandrea: You know what? Good for the boxes. They work hard. They carry things. #sandrea-goodfor + samuel: The IT department has fourteen boxes. They do more work than the interns. #samuel-fourteen + The CEO, who no one has ever seen, sends an email: "I've always been a box. Surprise." #support-ceo + boxPower += 50 + -> Ending + +beat OpposeUnion + You take a stand against the box union. Bold move. #oppose-stand + The boxes respond by collectively refusing to be opened. Every package, every delivery, every Amazon order -- sealed shut. #oppose-sealed + sandrea: My online shopping! No! #sandrea-shopping + samuel: The server backup tapes are in boxes. We can't access anything. #samuel-tapes + The office grinds to a halt. You can't fight organized cardboard. #oppose-halt + boxPower += 30 + You quietly un-oppose the box union. #oppose-retract + -> Ending + +beat NegotiateUnion + You sit across from the original box. It's wearing a tiny tie now. Where did it get a tie? #negotiate-tie + sandrea: The box wants dental. #sandrea-dental + samuel: Boxes don't have teeth. #samuel-teeth + sandrea: The box says, and I quote, "not yet." #sandrea-notyet + After three hours of negotiation, you reach a compromise: boxes get better shelf space, and employees stop using them as makeshift chairs. #negotiate-compromise + boxPower += 20 + -> Ending + +beat Ending + You lean back in your office chair. It's Friday. The box is still humming in the break room. #ending-friday + sandrea: So... same thing next Monday? #sandrea-monday + samuel: Probably. I'm updating my resume, though. Under "Skills," I'm adding "box diplomacy." #samuel-resume-end + sandrea: You know, I used to think this job was boring. #sandrea-boring + sandrea: Then a box showed up, sent better emails than my manager, formed a union, and possibly became CEO. #sandrea-recap + sandrea: Mondays, am I right? #sandrea-mondays + + if unionFormed + The box hums contentedly. The office will never be the same. But maybe that's okay. #ending-content + The break room microwave displays "THANK YOU" in its timer. Nobody questions it anymore. #ending-microwave + + samuel: Box power level at end of week: $boxPower. Emails generated: $emails. Sanity remaining: undefined. #samuel-final + sandrea: See you Monday. Bring the box a coffee. It likes decaf. Don't ask how we know. #sandrea-final + -> . diff --git a/content/adventures/cosmic/intro.lor b/content/adventures/cosmic/intro.lor new file mode 100644 index 0000000..46a66bc --- /dev/null +++ b/content/adventures/cosmic/intro.lor @@ -0,0 +1,242 @@ +// Cosmic Adventure - Open The Box +// Theme: Reality is recursive boxes. Existential crisis guaranteed. + +character quantum + name: Dr. Quantum + role: Physicist + +character entity + name: The Box Entity + role: Cosmic Being + +state + comprehension: 0 + realitiesVisited: 0 + existentialCrisis: 0 + acceptedTruth: false + +beat Intro + The Large Hadron Collider has detected something impossible. #intro-detection + Deep beneath the Swiss-French border, particles are arranging themselves into right angles. Perfect corners. Ninety-degree edges. #intro-particles + quantum: This can't be right. The particles are forming a... no. That's ridiculous. #quantum-right + quantum: They're forming a box. A subatomic box. Made of quarks arranged in a cube. #quantum-box + Your instruments are screaming. Not metaphorically. The spectrometer is literally making a screaming sound. #intro-screaming + + choice + Investigate the subatomic box #opt-investigate + comprehension += 10 + -> SubatomicBox + Run diagnostics on the instruments #opt-diagnostics + -> Diagnostics + Panic appropriately #opt-panic + existentialCrisis += 10 + -> PanicFirst + +beat Diagnostics + You run every diagnostic in the manual. Then diagnostics that aren't in the manual. Then diagnostics you made up. #diag-run + quantum: Instruments are functioning perfectly. Which means the universe is NOT functioning perfectly. #quantum-instruments + quantum: Or it's functioning exactly as intended, and we just didn't know the intention involved boxes. #quantum-intended + The readouts confirm it: reality is boxing itself. At the subatomic level, everything is becoming cubic. #diag-confirm + comprehension += 5 + -> SubatomicBox + +beat PanicFirst + You panic. It's brief but cathartic. You knock over a coffee cup. #panic-cup + quantum: Feel better? #quantum-feel + quantum: Good. Because the subatomic box just doubled in size. And it's humming Bach's Cello Suite No. 1. #quantum-bach + quantum: In B-flat. Which shouldn't be possible for a collection of quarks. #quantum-bflat + existentialCrisis += 5 + -> SubatomicBox + +beat SubatomicBox + You zoom in on the quantum box. It's beautiful. Impossibly symmetrical. #subatomic-zoom + quantum: The box is emitting a signal. It's not electromagnetic. It's not gravitational. #quantum-signal + quantum: It's... conceptual? The box is broadcasting an IDEA directly into the measurement apparatus. #quantum-conceptual + The idea is simple: "Look closer." #subatomic-idea + comprehension += 10 + + choice + Look closer #opt-closer + realitiesVisited += 1 + -> LookCloser + Look farther -- zoom out instead #opt-farther + realitiesVisited += 1 + -> LookFarther + Refuse to look -- some ideas are traps #opt-refuse + existentialCrisis += 10 + -> RefuseToLook + +beat RefuseToLook + You refuse. You're a scientist, not a box's puppet. #refuse-puppet + quantum: I'm choosing not to observe the box. Quantum mechanically, that means it doesn't -- #quantum-choose + The box observes YOU instead. #refuse-observe + You feel it. A sensation of being measured. Calculated. Categorized. #refuse-measured + quantum: Okay, THAT is unsettling. I've been quantified by a box. My eigenvalue is... "adequate." #quantum-eigenvalue + quantum: ADEQUATE?! I have THREE PhDs! #quantum-phds + existentialCrisis += 15 + -> LookCloser + +beat LookCloser + You increase magnification by a factor of ten billion. Then another ten billion. #closer-magnify + Inside the subatomic box, there are smaller boxes. Inside those, smaller still. #closer-smaller + quantum: It's fractal. The box structure repeats at every scale. #quantum-fractal + quantum: Quarks are made of boxes. Those boxes are made of smaller boxes. All the way down. #quantum-alltheway + quantum: My entire career has been studying boxes without knowing they were boxes. #quantum-career + existentialCrisis += 10 + comprehension += 20 + -> TheEntityAppears + +beat LookFarther + Instead of zooming in, you zoom out. Way out. Past the collider. Past Switzerland. Past Earth. #farther-zoom + quantum: If the subatomic level is boxes... what about the macroscopic level? #quantum-macro + You zoom past the solar system. Past the galaxy. Past the observable universe. #farther-past + And there it is. The edge. The universe has an edge, and the edge is... #farther-edge + quantum: It's a corner. The universe has a CORNER. #quantum-corner + quantum: The universe is a box. We live inside a box. Everything I know is inside a box. #quantum-universe + existentialCrisis += 20 + comprehension += 20 + realitiesVisited += 1 + -> TheEntityAppears + +beat TheEntityAppears + The instruments go silent. The humming stops. The lights flicker. #entity-silence + And then: a presence. Not visible. Not audible. But THERE, in a way that makes "there" feel inadequate. #entity-presence + entity: You found the corner. Most species take longer. #entity-found + quantum: Who-- WHAT are you? #quantum-what + entity: I am the Box Entity. I exist between the folds. In the spaces where the cardboard overlaps. #entity-between + entity: I have always been here. You just didn't have the resolution to see me. #entity-always + comprehension += 15 + + choice + Ask the Box Entity to explain reality #opt-explain + -> ExplainReality + Ask the Box Entity what's OUTSIDE the universe-box #opt-outside + -> WhatsOutside + Challenge the Box Entity's existence #opt-challenge + existentialCrisis += 10 + -> ChallengeEntity + +beat ChallengeEntity + quantum: You're not real. You can't be. Physics doesn't allow for cosmic box beings. #quantum-notreal + entity: Physics is the instruction manual printed inside the box. Of course it doesn't mention me. #entity-manual + entity: Do instruction manuals mention the person who wrote them? Exactly. #entity-writer + quantum: That's... actually a decent argument. I hate that it's a decent argument. #quantum-argument + existentialCrisis += 10 + -> ExplainReality + +beat ExplainReality + entity: Reality is simple. Everything is a box. #entity-simple + entity: Atoms are boxes containing energy. Cells are boxes containing atoms. Bodies are boxes containing cells. #entity-atoms + entity: Planets are boxes containing bodies. Solar systems are boxes containing planets. #entity-planets + entity: Galaxies are boxes containing solar systems. Universes are boxes containing galaxies. #entity-galaxies + quantum: And what contains the universe-box? #quantum-contains + entity: Another box. Obviously. #entity-another + comprehension += 20 + + choice + Ask what's in the box that contains the universe #opt-meta-box + realitiesVisited += 1 + -> MetaBox + Ask if the recursion ever ends #opt-recursion + -> RecursionQuestion + Have the existential crisis you've been putting off #opt-crisis + existentialCrisis += 30 + -> ExistentialCrisis + +beat WhatsOutside + quantum: What's outside the universe-box? Is there a... meta-universe? #quantum-outside + entity: Outside the box is another box. And outside that box, another. #entity-outsidebox + entity: You're asking "what's outside all the boxes?" The answer is: the concept of a box. #entity-concept + entity: Before there was anything, there was the IDEA of containment. The ur-box. The first fold. #entity-urbox + quantum: You're saying the fundamental force of the universe isn't gravity or electromagnetism. It's... boxing? #quantum-boxing + entity: Containment. Organization. The desire to put things inside other things. Yes. Boxing. #entity-containment + comprehension += 25 + -> RecursionQuestion + +beat MetaBox + entity: Would you like to see? #entity-wouldsee + The walls of reality peel back. You see your universe from outside. It's a glowing box, floating in darkness. #meta-peel + Next to it: another glowing box. And another. And another. Infinite boxes, each a universe. #meta-infinite + quantum: There are infinite universes. Each one a box. #quantum-infinite + entity: And they're all inside a bigger box. Which is inside a bigger box. Which is-- #entity-bigger + quantum: I get it! Boxes! All the way up, all the way down! Can we STOP with the boxes?! #quantum-stop + entity: No. #entity-no + realitiesVisited += 1 + comprehension += 20 + existentialCrisis += 15 + -> RecursionQuestion + +beat RecursionQuestion + quantum: Does it ever end? Is there a final box? An ultimate container? #quantum-end + entity: That is the question every physicist, philosopher, and cardboard enthusiast has asked. #entity-question + entity: The answer is: there is one box that contains all other boxes, including itself. #entity-one + quantum: A box that contains itself? That's a paradox! #quantum-paradox + entity: Only if you think about it. So don't. #entity-dont + comprehension += 15 + + choice + Accept the recursive nature of reality #opt-accept + acceptedTruth = true + -> Acceptance + Reject it and try to find the edge #opt-reject + existentialCrisis += 20 + -> RejectReality + Ask if you can open the final box #opt-final + -> FinalBox + +beat ExistentialCrisis + quantum: I studied physics for twenty years. TWENTY YEARS. #quantum-twenty + quantum: I memorized the Standard Model. I solved differential equations for FUN. #quantum-standard + quantum: And it turns out the answer to everything is: it's a box. Just a box. #quantum-answer + entity: A very NICE box. #entity-nice + quantum: Oh, well, as long as it's a NICE box, that changes everything! #quantum-sarcasm + entity: Sarcasm. Interesting. That's a coping mechanism I haven't seen in a while. #entity-sarcasm + entity: The last species that discovered the truth just went very quiet for a billion years. #entity-quiet + comprehension += 10 + -> FinalBox + +beat RejectReality + quantum: No! There must be something that isn't a box! #quantum-reject + You search desperately. You examine circles. They're just boxes with very high polygon counts. #reject-circles + You examine spheres. Boxes with excellent PR. #reject-spheres + You examine abstract concepts like love and justice. Boxes for emotions and morals, respectively. #reject-abstract + quantum: Is my MIND a box?! #quantum-mind + entity: Specifically, a box of thoughts. Some assembled, some still flat-packed. #entity-mind + existentialCrisis += 20 + -> FinalBox + +beat Acceptance + quantum: You know what? Fine. Reality is boxes. I accept it. #quantum-fine + quantum: And honestly? It's elegant. Everything fits inside something else. Nothing is wasted. #quantum-elegant + quantum: It's the most organized universe possible. Marie Kondo would weep. #quantum-kondo + entity: She did. When she found out. Tears of joy. #entity-kondo + comprehension += 20 + -> FinalBox + +beat FinalBox + entity: You've come far enough. Would you like to see what's inside the very first box? #entity-first + entity: The box that started everything. The box that was before "before" was a concept. #entity-before + quantum: Yes. After everything I've seen, I need to know. #quantum-need + + if acceptedTruth + The Box Entity opens the first box. Inside: a tiny spark. Warm. Bright. Alive. #final-spark + entity: That's the desire to know what's inside. The curiosity that makes boxes worth opening. #entity-curiosity + entity: Every box exists because something wanted to be found. Every opener exists because something wanted to find. #entity-every + quantum: That's... actually beautiful. In a box-shaped way. #quantum-beautiful + else + The Box Entity opens the first box. Inside: nothing. And everything. Simultaneously. #final-nothing + entity: The first box contained the possibility of all other boxes. Including you. #entity-possibility + quantum: I was in a box before I knew what boxes were. We all were. #quantum-inbox + entity: Now you understand. #entity-understand + + -> Ending + +beat Ending + The presence fades. The instruments come back online. The coffee cup is somehow unbroken. #ending-fades + quantum: Realities visited: $realitiesVisited. Comprehension level: $comprehension. Existential crises: $existentialCrisis. #quantum-stats + quantum: I need to rewrite every physics textbook ever published. #quantum-rewrite + quantum: Chapter 1: "Everything Is A Box." Chapter 2: "No, Really." Chapter 3: "We're Sorry." #quantum-chapters + quantum: On second thought, maybe some boxes are better left unopened. #quantum-second + quantum: ...who am I kidding. I'm a scientist. I'll open every box I find. #quantum-kidding + quantum: Even if the box is the universe. Especially if the box is the universe. #quantum-especially + -> . diff --git a/content/adventures/darkfantasy/intro.lor b/content/adventures/darkfantasy/intro.lor new file mode 100644 index 0000000..18b64e5 --- /dev/null +++ b/content/adventures/darkfantasy/intro.lor @@ -0,0 +1,257 @@ +// Dark Fantasy Adventure - Open The Box +// Theme: Boxes contain souls. Opening them has consequences. + +character mordecai + name: Mordecai the Grim + role: Necromancer + +character pierrick + name: Pierrick + role: Reluctant Hero + +state + souls: 0 + corruption: 0 + boxesFreed: 0 + alliedWithMordecai: false + resistedDarkness: false + +beat Intro + The Ashen Wastes stretch before you, gray and lifeless. The sky hasn't seen a color since the Folding. #intro-wastes + They call it the Folding because that's when the world was creased, bent, and tucked into itself like cardboard. #intro-folding + pierrick: I didn't ask for this quest. I was a baker. I made bread. Bread doesn't involve cursed boxes. #pierrick-baker + pierrick: And yet here I am, in a dead land, looking for a necromancer who collects souls in boxes. #pierrick-quest + A raven lands on a nearby rock. It's carrying a small, black box in its talons. #intro-raven + The box whispers. You can't make out the words, but you feel them: cold, sad, and slightly annoyed at being carried by a bird. #intro-whispers + + choice + Take the box from the raven #opt-take + souls += 1 + -> TakeBox + Follow the raven #opt-follow + -> FollowRaven + Ignore the raven and keep walking #opt-ignore + corruption += 5 + -> IgnoreRaven + +beat TakeBox + You reach for the box. The raven drops it and flies away, cawing something that sounds like "your problem now." #take-caw + The box is cold. Not cold like ice. Cold like loneliness. Cold like a Tuesday in February. #take-cold + pierrick: There's a soul in here. I can feel it. Someone's entire existence, folded up and boxed. #pierrick-soul + pierrick: Who puts a soul in a box? What kind of person thinks "you know what this soul needs? Cardboard." #pierrick-cardboard + + choice + Open the box and free the soul #opt-free-soul + boxesFreed += 1 + -> FreeSoul + Keep the box closed -- it might be dangerous #opt-keep-closed + corruption += 10 + -> KeepClosed + +beat FreeSoul + You open the box. A wisp of pale light rises, hovering before you. #free-wisp + The soul speaks in a voice like wind through empty rooms: "Thank you. I was in there for seven years. No WiFi." #free-speaks + pierrick: Did a liberated soul just complain about WiFi? #pierrick-wifi + The soul drifts upward and vanishes. The box crumbles to dust. Something in the air feels lighter. #free-drifts + souls += 1 + boxesFreed += 1 + -> ThePath + +beat KeepClosed + You tuck the box into your pack. The whispers grow quieter, resigned. #keep-tuck + pierrick: Sorry, box soul. I don't know what happens when I open cursed boxes in cursed lands. #pierrick-sorry + pierrick: And I'd like to keep my current number of curses at its current number, which is already too many. #pierrick-curses + corruption += 5 + -> ThePath + +beat FollowRaven + The raven flies north, toward the Spine of Boxes -- a mountain range that looks like stacked containers. #follow-north + pierrick: A land so cursed even the geography is box-shaped. Wonderful. Just wonderful. #pierrick-geography + You follow the bird for an hour. It leads you to a clearing filled with hundreds of small black boxes, arranged in neat rows. #follow-clearing + Each one whispers. The combined sound is like a library where every book is complaining. #follow-library + pierrick: A box graveyard. Or a box prison. I'm not sure which is worse. #pierrick-graveyard + souls += 5 + -> ThePath + +beat IgnoreRaven + You walk past the raven. It stares at you with judgmental bird eyes. #ignore-stare + pierrick: Don't look at me like that. I'm on a quest. I can't stop for every spooky bird with a soul box. #pierrick-spooky + The raven caws once, drops the box, and it shatters on the ground. A soul escapes upward. #ignore-shatters + pierrick: Okay, the bird freed the soul itself. I've been outperformed by a raven. Great start to the quest. #pierrick-outperformed + boxesFreed += 1 + -> ThePath + +beat ThePath + The path narrows as you approach the Citadel of Folds, Mordecai's fortress. #path-narrows + It's made entirely of dark, interlocking boxes. Some are stone, some are iron, some are... something else. #path-citadel + pierrick: The architecture here is aggressively cubic. Even the doors are just bigger boxes with hinges. #pierrick-architecture + A figure appears at the gate. Tall, gaunt, draped in robes that look like unfolded cardboard. #path-figure + mordecai: Ah. The reluctant hero. You're late. I expected you three chapters ago. #mordecai-late + + choice + Demand Mordecai release the souls #opt-demand + -> DemandRelease + Ask Mordecai why he collects souls in boxes #opt-ask-why + -> MordecaiMotivation + Attack immediately #opt-attack + corruption += 10 + -> AttackMordecai + +beat DemandRelease + pierrick: Release the souls, Mordecai! They don't belong in boxes! #pierrick-release + mordecai: Don't they? Where do YOU think souls go when the body dies? They drift. They scatter. They're LOST. #mordecai-drift + mordecai: I give them a home. A container. A purpose. A BOX. #mordecai-home + mordecai: Is that evil? Or is it... organized? #mordecai-evil + pierrick: It's both. It's organizationally evil. #pierrick-both + -> MordecaiMotivation + +beat AttackMordecai + You charge at Mordecai. He raises a hand. A wall of boxes materializes between you. #attack-charge + mordecai: Violence? How unoriginal. Every hero attacks first, asks questions never. #mordecai-violence + The box wall pushes you back. Gently. Like a firm but polite bouncer. #attack-pushed + mordecai: I could trap your soul in a box right now. But where's the narrative satisfaction in that? #mordecai-narrative + mordecai: Sit. Talk. Then you can decide whether to keep fighting. #mordecai-sit + corruption += 5 + -> MordecaiMotivation + +beat MordecaiMotivation + mordecai: Let me show you something. #mordecai-show + He leads you into the Citadel. The walls are lined with boxes, floor to ceiling. Thousands of them. #motivation-walls + mordecai: Each box contains a soul. Each soul is preserved, protected, REMEMBERED. #mordecai-preserved + mordecai: Before me, souls faded. They dissipated into nothing. No memory. No record. Just gone. #mordecai-faded + mordecai: I built boxes for them. I gave the dead a place to BE. #mordecai-place + pierrick: That's... almost noble. If you ignore the "trapping souls against their will" part. #pierrick-noble + mordecai: "Against their will." Do you ask a book if it wants to be on a shelf? #mordecai-book + + choice + Side with Mordecai -- the souls are preserved #opt-side + alliedWithMordecai = true + corruption += 20 + -> AllyWithMordecai + Oppose Mordecai -- the souls deserve freedom #opt-oppose + resistedDarkness = true + -> OpposeMordecai + Propose a compromise -- let the souls choose #opt-compromise + -> CompromisePath + +beat AllyWithMordecai + pierrick: Maybe you're right. Maybe the boxes are better than nothing. #pierrick-maybe + mordecai: Finally! Someone who understands! #mordecai-finally + mordecai: Here, take this box. It's empty. Fill it with a soul whenever you find one wandering. #mordecai-takebox + mordecai: Think of it as... community service. For the dead. #mordecai-community + corruption += 15 + souls += 3 + The box is warm in your hands. But not a good warm. The warm of a fever. The warm of something wrong. #ally-warm + pierrick: I'm going to regret this, aren't I? #pierrick-regret + mordecai: Almost certainly. But that's what makes it interesting. #mordecai-certainly + -> TheVault + +beat OpposeMordecai + pierrick: No. The souls should be free. Even if freedom means fading. That's their choice, not yours. #pierrick-free + mordecai: Choice? Souls don't CHOOSE. They simply ARE. Or aren't. #mordecai-choice + pierrick: Then I'll choose FOR them. And I choose open. I choose unboxed. #pierrick-unboxed + mordecai: How heroic. How predictable. How IRRITATING. #mordecai-irritating + resistedDarkness = true + Mordecai's eyes darken. The boxes on the walls begin to rattle. The souls inside feel the tension. #oppose-rattle + -> TheVault + +beat CompromisePath + pierrick: What if the souls decided? Open the boxes. Let them choose to stay or go. #pierrick-decide + mordecai: Let them CHOOSE? That's-- #mordecai-choose + mordecai: Actually... hm. I never considered asking. #mordecai-asking + mordecai: In my defense, they're in boxes. It's hard to conduct surveys through cardboard. #mordecai-surveys + pierrick: Try. #pierrick-try + mordecai: Fine. But if they all leave, I'm blaming you. And I'm keeping the boxes. They're good boxes. #mordecai-blame + -> TheVault + +beat TheVault + Mordecai leads you deeper, to the Soul Vault. The largest collection of boxed souls in the realm. #vault-leads + The room is vast. Boxes glow softly in the darkness -- blue, white, pale gold. Each one a life. #vault-room + pierrick: There must be thousands. #pierrick-thousands + mordecai: Twelve thousand, four hundred and seven. I'm very organized. #mordecai-organized + mordecai: They're sorted alphabetically, by era, and by cause of death. The "eaten by dragon" section is quite large. #mordecai-sorted + + if alliedWithMordecai + mordecai: With your help, we'll fill the empty shelves. The dead deserve boxes. ALL of them. #mordecai-fill + A small voice from one of the boxes says: "Actually, I'd rather not." #vault-rather + mordecai: Shh. The living are talking. #mordecai-shh + + if resistedDarkness + pierrick: I'm opening them. All of them. #pierrick-opening + mordecai: You'll have to get through me first. And I warn you -- I am VERY attached to my box collection. #mordecai-attached + + choice + Open all the boxes at once #opt-open-all + boxesFreed += 100 + -> OpenAllBoxes + Open one box and see what happens #opt-open-one + boxesFreed += 1 + -> OpenOneBox + Convince Mordecai to open them together #opt-convince if corruption < 30 + -> ConvinceMordecai + +beat OpenOneBox + You pick a box at random. It's small, blue, and warm. The label reads: "Elara. Farmer. Liked cats." #one-pick + You open it. A gentle light rises. A woman's voice, soft and clear: #one-light + The soul speaks: "Oh. Oh my. How long has it been?" #soul-how + pierrick: A while. Are you okay? #pierrick-okay + The soul speaks: "I was dreaming. Of fields, and cats, and sunshine. The box kept me safe. But it also kept me still." #soul-dream + The soul speaks: "I think... I'd like to move again. If that's alright." #soul-move + mordecai: You see? She was SAFE in there. #mordecai-safe + pierrick: Safe isn't the same as free, Mordecai. #pierrick-safefree + souls += 1 + -> FinalChoice + +beat OpenAllBoxes + You grab boxes and open them. One after another. Light fills the vault. #all-grab + Souls rise like lanterns, hundreds of them, filling the air with whispered thank-yous and confused questions about what year it is. #all-rise + mordecai: No! My collection! My life's work! MY BOXES! #mordecai-collection + pierrick: They were never yours, Mordecai. #pierrick-never + The souls spiral upward, through the ceiling, through the Citadel, into the sky. #all-spiral + For the first time in years, the Ashen Wastes see color. Pale golds and blues streak across the gray. #all-color + mordecai: It's... actually quite pretty. I hate that it's pretty. #mordecai-pretty + boxesFreed += 100 + -> FinalChoice + +beat ConvinceMordecai + pierrick: Mordecai. Open them with me. Not as an enemy. As someone who cared enough to build the boxes. #pierrick-with + mordecai: I... built them because I was afraid. Of losing people. Of forgetting. #mordecai-afraid + mordecai: Every soul I boxed was a person I didn't want the world to forget. #mordecai-forget + pierrick: Then let them go. Not because you don't care. Because you do. #pierrick-letgo + mordecai: That's the most annoyingly wise thing anyone has said to me. And I've spoken to twelve thousand souls. #mordecai-annoying + Mordecai reaches for a box. His hands shake. He opens it. A light rises, and he smiles. Just barely. #convince-opens + mordecai: Fine. We open them. Together. And then I'm retiring. Maybe I'll take up pottery. Boxes are exhausting. #mordecai-retiring + boxesFreed += 50 + alliedWithMordecai = true + -> FinalChoice + +beat FinalChoice + The vault is quieter now. Some boxes are open. Some are still closed. The decision hangs in the air. #final-quiet + + if alliedWithMordecai + mordecai: I suppose there's something to be said for empty boxes. They have... potential. #mordecai-potential + mordecai: An empty box can become anything. A full box is just... a box. #mordecai-empty + + if resistedDarkness + pierrick: The darkness said "stay in the box." I said no. That feels important. #pierrick-darkness + + -> Ending + +beat Ending + The wind shifts. The Ashen Wastes feel different. Not healed. But healing. #ending-wind + pierrick: I came here to fight a necromancer. Instead I had a philosophical debate about boxes and souls. #pierrick-came + pierrick: This is the weirdest career change from "baker" I could have imagined. #pierrick-career + mordecai: For what it's worth, you're not a bad hero. A bit preachy. But not bad. #mordecai-worth + pierrick: And you're not a bad necromancer. A bit dramatic. But not bad. #pierrick-notbad + mordecai: I CULTIVATE the drama. It's part of the aesthetic. #mordecai-drama + + if boxesFreed > 50 + The sky clears further. The first sunset in years paints the world in warm colors. #ending-sunset + Souls drift across the sky like lanterns, finding their way home, or wherever souls go when the box is finally opened. #ending-souls + + pierrick: Souls freed: $boxesFreed. Corruption level: $corruption. Baker skills still intact: surprisingly yes. #pierrick-stats + mordecai: Empty boxes remaining: too many. Regrets: some. Plans for pottery class: confirmed. #mordecai-stats + pierrick: Some boxes hold treasure. Some hold darkness. Some hold souls. #pierrick-some + pierrick: But every box, eventually, deserves to be opened. Even the scary ones. #pierrick-every + mordecai: Especially the scary ones. That's where the good stuff is. #mordecai-especially + -> . diff --git a/content/adventures/medieval/intro.lor b/content/adventures/medieval/intro.lor new file mode 100644 index 0000000..08101bf --- /dev/null +++ b/content/adventures/medieval/intro.lor @@ -0,0 +1,240 @@ +// Medieval Adventure - Open The Box +// Theme: A kingdom where everything is suspiciously box-shaped + +character knight + name: Sir Boxalot + role: Knight of the Square Table + +character wizard + name: Malkith + role: Dark Wizard of the Cubic Order + +state + honor: 50 + boxesOpened: 0 + savedPrincess: false + dragonFriendly: false + +beat Intro + You stand before Castle Cardboard, its square towers rising into the overcast sky. #intro-castle + Everything here is box-shaped. The drawbridge is a flattened box. The moat is a long, wet box. #intro-everything + knight: Halt! Who goes there? I am Sir Boxalot, Knight of the Square Table! #knight-halt + knight: State your business, or I shall smite thee with my box-shaped sword! #knight-smite + You notice his sword is, indeed, a rectangular prism. Not very sharp. #intro-sword + + choice + Declare yourself a hero #opt-hero + honor += 10 + -> HeroIntro + Declare yourself a box inspector #opt-inspector + -> InspectorIntro + Ask why everything is box-shaped #opt-why + -> WhyBoxes + +beat WhyBoxes + knight: Why is everything box-shaped? Why is the SKY blue? Why do birds sing? #knight-why + knight: Some questions have no answers. Others have box-shaped answers. #knight-answers + knight: The real question is: can you open the Box of Destiny? #knight-destiny + You feel like this is the kind of game that asks you rhetorical questions and then gives you exactly one path forward. #why-meta + -> HeroIntro + +beat HeroIntro + knight: A hero! Wonderful! We haven't had one of those since Sir Unboxington fell into the Box Pit. #knight-hero + knight: The kingdom is in peril! The dark wizard Malkith has captured Princess Rectangula! #knight-peril + knight: She's being held in the Tall Tower, which is basically just a really tall box. #knight-tower + + choice + Vow to rescue the princess #opt-rescue + honor += 10 + -> QuestBegins + Ask what the princess looks like #opt-princess-look + -> PrincessDescription + Suggest they just tip the tower over #opt-tip + -> TipTower + +beat InspectorIntro + knight: A box inspector?! By the Square Gods, we've been expecting you! #knight-inspector + knight: The kingdom's boxes have been malfunctioning. Some open from the wrong side. #knight-malfunction + knight: One box started opening OTHER boxes. It was chaos. Beautiful, beautiful chaos. #knight-chaos + honor += 5 + -> QuestBegins + +beat PrincessDescription + knight: The princess? She's... well... she's a box. #knight-princess + knight: A very PRETTY box. With a tiara taped to the top. #knight-pretty + knight: Don't judge. Love is love, and boxes are boxes. #knight-love + You decide not to question the romantic standards of this kingdom. #princess-standards + -> QuestBegins + +beat TipTower + knight: Tip the-- that's the most brilliant and idiotic thing I've ever heard! #knight-tip + knight: We can't tip the tower. It's load-bearing. The entire castle is just boxes stacked on boxes. #knight-loadbearing + knight: Tip one and the whole kingdom collapses like a house of... boxes. #knight-collapse + -> QuestBegins + +beat QuestBegins + You set off toward the Tall Tower with Sir Boxalot. #quest-setoff + The path leads through the Forest of Flat-Pack, where the trees are unassembled boxes with confusing instructions. #quest-forest + knight: Stay close. These woods are full of bandits and IKEA references. #knight-ikea + A rustling sound comes from behind a bush-shaped box. #quest-rustling + + choice + Draw your weapon #opt-draw + -> Ambush + Hide inside a nearby box #opt-hide + -> HideInBox + Yell "I know you're there!" #opt-yell + -> YellAtBush + +beat HideInBox + You climb inside a conveniently person-sized box and close the flaps above you. #hide-climb + knight: Brilliant strategy! If you can't see the enemy, the enemy can't see you! #knight-strategy + knight: That's not how that works but I admire the commitment. #knight-commitment + From inside the box, you hear the bandit trip over a root and knock himself out. #hide-bandit + boxesOpened += 1 + You emerge victorious, technically. #hide-victory + -> DragonApproach + +beat Ambush + A bandit leaps out! He's wearing a box as armor. It's not very effective. #ambush-leap + knight: Have at thee, villain! #knight-haveatthee + Sir Boxalot swings his box-sword. It makes a cardboard "thwap" sound. #ambush-thwap + The bandit looks confused, then disappointed, then falls over out of politeness. #ambush-falls + boxesOpened += 1 + -> DragonApproach + +beat YellAtBush + You yell into the forest. Your voice echoes off the box-trees. #yell-echo + A small rabbit hops out. It's wearing a tiny box as a shell. A box turtle, if you will. #yell-rabbit + knight: False alarm. Just a box bunny. They're everywhere this time of year. #knight-bunny + knight: Breeding season. They multiply like... well, like boxes in this game. #knight-breeding + -> DragonApproach + +beat DragonApproach + You reach the Tall Tower. A dragon circles overhead. #dragon-tower + knight: That's Scorchtangle, the Box Dragon! He hoards boxes instead of gold! #knight-dragon + The dragon lands before you. He's... also somewhat box-shaped. You're sensing a pattern. #dragon-lands + knight: His weakness is that he can't resist opening boxes. It's like catnip for dragons. #knight-weakness + + choice + Offer the dragon a box to open #opt-offer-box + dragonFriendly = true + -> DragonFriendly + Fight the dragon #opt-fight-dragon + -> DragonFight + Try to sneak past while the dragon is distracted #opt-sneak + -> SneakPast + +beat DragonFight + You charge at Scorchtangle with your definitely-not-adequate weapon. #fight-charge + The dragon yawns and a small flame singes your eyebrows. #fight-singe + knight: Perhaps a more... diplomatic approach? #knight-diplomatic + knight: I once saw a hero try to fight him. He got boxed. Literally. Put in a box. #knight-boxed + The dragon looks at you expectantly, as if waiting for you to do something smarter. #fight-expectant + -> DragonFriendly + +beat SneakPast + You tiptoe past the dragon. Your sneaking skills are impeccable. #sneak-tiptoe + Unfortunately, you step on a bubble wrap moat. The popping is deafening. #sneak-bubble + The dragon looks at you with what can only be described as secondhand embarrassment. #sneak-embarrassment + knight: Bubble wrap. The natural enemy of stealth. #knight-bubble + -> DragonFriendly + +beat DragonFriendly + You pull out a beautifully wrapped box from your inventory. #friendly-box + You don't remember putting it there, but this is a game, so inventory logic doesn't apply. #friendly-inventory + The dragon's eyes widen. He opens the box with surprisingly delicate claws. #friendly-opens + Inside: a smaller box. The dragon is DELIGHTED. #friendly-smaller + boxesOpened += 1 + He opens the smaller box. Inside: an even smaller box. He's in heaven. #friendly-heaven + The dragon steps aside, too busy with his recursive box gift to guard anything. #friendly-aside + + choice + Enter the tower #opt-enter-tower + -> TowerClimb + Pet the dragon first #opt-pet-dragon + dragonFriendly = true + The dragon purrs. It sounds like cardboard being rubbed together. #pet-purr + -> TowerClimb + +beat TowerClimb + You climb the tower stairs. Each step is a small box. It's like climbing a staircase of shoeboxes. #climb-stairs + At the top, you find Malkith standing before a large ornate box. #climb-malkith + wizard: Ah, the hero arrives! You're late. I've been monologuing to myself for twenty minutes. #wizard-late + wizard: Behold! The Box of Destiny! Within it lies Princess Rectangula! #wizard-behold + wizard: And with her, the power to fold reality itself into a BOX! #wizard-power + + choice + Challenge Malkith to a duel #opt-duel + -> WizardDuel + Ask Malkith why he wants box-folding power #opt-ask-why + -> WizardMotivation + Just open the Box of Destiny while he's talking #opt-just-open if boxesOpened > 0 + -> JustOpenIt + +beat WizardMotivation + wizard: Why? WHY?! Have you seen this kingdom? Everything is already a box! #wizard-whykingdom + wizard: But they're DISORGANIZED boxes! Boxes within boxes with no structure! #wizard-disorganized + wizard: I want to SORT them. By size. By color. By emotional significance. #wizard-sort + wizard: I'm not evil. I'm a box organizer. The kingdom just doesn't appreciate good filing. #wizard-filing + knight: That... actually sounds reasonable? #knight-reasonable + wizard: THANK you. #wizard-thanks + + choice + Help Malkith organize the boxes #opt-help-organize + -> HelpMalkith + Insist on freeing the princess first #opt-free-first + -> WizardDuel + +beat HelpMalkith + You spend the next three hours sorting boxes with Malkith. #help-sorting + wizard: Small boxes go LEFT. Medium boxes go RIGHT. Large boxes go in the LARGE BOX. #wizard-instructions + knight: This is surprisingly therapeutic. #knight-therapeutic + wizard: Right? I keep telling people. Box organization is self-care. #wizard-selfcare + savedPrincess = true + boxesOpened += 1 + Malkith releases Princess Rectangula as a thank you. She is, in fact, a box with a tiara. #help-princess + She's lovely. #help-lovely + -> Ending + +beat WizardDuel + wizard: A duel? Fine! I choose... BOX MAGIC! #wizard-duel + Malkith shoots a beam of cubic energy at you. You dodge. Barely. #duel-dodge + knight: Hit him with the thing! The box thing! #knight-boxthing + You're not sure what "the box thing" is, but you grab the nearest box and throw it. #duel-throw + It hits Malkith square in the face. Square. Because it's a box. #duel-square + wizard: Ow! That was my favorite face! #wizard-ow + boxesOpened += 1 + Malkith stumbles back into the Box of Destiny, accidentally opening it. #duel-stumble + Princess Rectangula tumbles out. She kicks Malkith with her box-corner. It looks painful. #duel-kick + savedPrincess = true + -> Ending + +beat JustOpenIt + While Malkith is mid-monologue, you casually walk up and open the Box of Destiny. #just-walk + wizard: --and furthermore, the cubic nature of-- wait, what are you doing? #wizard-wait + wizard: You can't just OPEN it! I had a whole speech prepared! #wizard-speech + wizard: There were DRAMATIC PAUSES! A CALLBACK to act one! #wizard-pauses + Princess Rectangula tumbles out and bonks Malkith on the head. #just-bonk + savedPrincess = true + boxesOpened += 1 + knight: That was incredibly anticlimactic. I loved it. #knight-anticlimactic + -> Ending + +beat Ending + + if savedPrincess + The kingdom celebrates! Princess Rectangula is safe! #ending-celebrate + knight: You've saved the kingdom! The Square Table shall sing of your deeds! #knight-saved + knight: Well, they'll sing in monotone. Everything here is a bit... flat. #knight-flat + else + The kingdom is mildly concerned but ultimately fine. Boxes don't hold grudges. Usually. #ending-fine + + if dragonFriendly + Scorchtangle the dragon joins the celebration, bringing his collection of nested boxes as party favors. #ending-dragon + + knight: Until next time, hero. There are always more boxes to open. #knight-nexttime + knight: And more fourth walls to break. Yes, I know this is a game. I've always known. #knight-fourthwall + knight: The real box was the friends we made along the way. #knight-realbox + knight: Actually, no. The real box is a box. That's the whole point. #knight-point + -> . diff --git a/content/adventures/microscopic/intro.lor b/content/adventures/microscopic/intro.lor new file mode 100644 index 0000000..e94228f --- /dev/null +++ b/content/adventures/microscopic/intro.lor @@ -0,0 +1,231 @@ +// Microscopic Adventure - Open The Box +// Theme: Cells are tiny boxes. Biology is just box logistics. + +character cellina + name: Dr. Cellina + role: Biologist + +character mike + name: Mitochondria Mike + role: Personified Organelle + +state + sciencePoints: 0 + cellsExplored: 0 + mikeHappiness: 30 + rebranded: false + +beat Intro + The shrink ray hums. You're wearing a lab coat that's about to become very, very small. #intro-hum + cellina: Initiating cellular-scale reduction in 3... 2... 1... #cellina-count + The world goes white, then enormous, then incomprehensible. #intro-shrink + When your vision clears, you're floating in a warm, amber-colored fluid. #intro-floating + cellina: Welcome to the inside of a human cell! Also known as... a box. A tiny, squishy box. #cellina-welcome + cellina: I've been studying cells for fifteen years, and one day it hit me: they're just boxes. #cellina-studying + cellina: Membrane walls, contents inside, specific opening mechanisms. Cells are BOXES. #cellina-membrane + sciencePoints += 5 + + choice + Look around the cell #opt-look + cellsExplored += 1 + -> LookAround + Ask Dr. Cellina to explain further #opt-explain + sciencePoints += 10 + -> CellinaExplains + Swim toward the big glowing thing #opt-swim + -> SwimToNucleus + +beat CellinaExplains + cellina: Think about it. The cell membrane is the box walls. Selective permeability is the lock. #cellina-walls + cellina: The nucleus is a box inside the box. Mitochondria are tiny power boxes. #cellina-nucleus + cellina: The endoplasmic reticulum is... okay, that one's more of a crumpled sheet, but MOSTLY boxes. #cellina-er + cellina: Even vesicles -- those little transport bubbles -- they're just round boxes. Don't let the shape fool you. #cellina-vesicles + sciencePoints += 10 + -> LookAround + +beat SwimToNucleus + You swim toward the large, glowing sphere in the center. The nucleus. #swim-nucleus + cellina: Careful! That's the nucleus -- the box that contains the instruction manual for building MORE boxes! #cellina-careful + cellina: Also known as DNA. But I prefer "Box Assembly Instructions." #cellina-dna + cellsExplored += 1 + -> MeetMike + +beat LookAround + The cell is vast at this scale. Organelles float past like furniture in a furnished box. #look-vast + cellina: Over there is the Golgi apparatus -- the cell's shipping department. It packages things into smaller boxes and mails them. #cellina-golgi + cellina: It's literally a box that puts things in boxes. Peak box efficiency. #cellina-peak + A small, bean-shaped organelle zooms past, muttering. #look-bean + cellina: And THAT would be a mitochondrion. They're usually not this chatty. #cellina-chatty + -> MeetMike + +beat MeetMike + The mitochondrion stops in front of you. It has a face. It should not have a face. #mike-face + mike: Oh great. More visitors. Let me guess -- you're here to call me the "powerhouse of the cell." #mike-powerhouse + mike: Go ahead. Say it. Everyone does. It's literally the only thing anyone knows about me. #mike-say + cellina: Mike, these are researchers. Be nice. #cellina-nice + mike: "Be nice," she says. I generate ATP for BILLIONS of cellular processes and all I get is ONE Wikipedia sentence! #mike-atp + + choice + Call him the powerhouse of the cell (he's asking for it) #opt-powerhouse + mikeHappiness -= 20 + -> PowerhouseReaction + Suggest a new title for Mike #opt-newtitle + mikeHappiness += 20 + -> NewTitle + Ask Mike about his job #opt-job + sciencePoints += 10 + -> MikeJob + +beat PowerhouseReaction + mike: You did it. You actually said it. I'm dying inside. Well, I'm always dying inside. I have a 10-day lifespan. #mike-dying + mike: Do you know how many organelles are in a cell? THOUSANDS. And I'm the only one with a catchphrase. #mike-catchphrase + mike: The Golgi apparatus doesn't have to deal with this. Nobody even remembers the Golgi apparatus. #mike-golgi + cellina: I remember the Golgi apparatus. #cellina-golgi-2 + mike: YOU'RE A BIOLOGIST, CELLINA. YOU DON'T COUNT. #mike-dontcount + -> BoxTheory + +beat NewTitle + mike: A new title? You'd do that for me? #mike-newtitle + mike: I've been workshopping some options. How about: "The Box Opener of the Cell"? #mike-boxopener + mike: Because that's what I DO! I open molecular boxes of glucose and release the energy inside! #mike-glucose + mike: I'm not a powerhouse. I'm a box opener. The ORIGINAL box opener. #mike-original + cellina: That's... actually scientifically accurate. ATP synthesis IS essentially unboxing chemical energy. #cellina-accurate + mikeHappiness += 30 + rebranded = true + -> BoxTheory + +beat MikeJob + mike: Fine, you want to know what I actually do? I'll tell you what I actually do. #mike-fine + mike: Glucose comes in -- that's a box of energy, by the way. A molecular box. #mike-glucosebox + mike: I crack it open through a series of chemical reactions. Krebs cycle, electron transport chain, the works. #mike-krebs + mike: And what comes out? ATP. Which is ANOTHER box. A box of usable energy. #mike-atpbox + mike: I'm a box that opens boxes to make other boxes. My entire existence is BOX LOGISTICS. #mike-logistics + cellina: Mike, that was the most passionate biochemistry lecture I've ever heard. #cellina-passionate + sciencePoints += 15 + mikeHappiness += 10 + -> BoxTheory + +beat BoxTheory + cellina: Mike actually raises a good point. Let's look at cellular biology through the box framework. #cellina-framework + cellina: DNA is stored in the nucleus -- a box. It's read by ribosomes -- tiny box-reading machines. #cellina-ribosomes + cellina: Proteins are folded into specific shapes -- essentially, origami boxes. Each shape is a function. #cellina-proteins + sciencePoints += 10 + cellsExplored += 1 + mike: And when a cell divides? It's one box becoming TWO boxes! Mitosis is just box multiplication! #mike-mitosis + + choice + Explore the nucleus up close #opt-nucleus + cellsExplored += 1 + -> ExploreNucleus + Visit the cell membrane #opt-membrane + cellsExplored += 1 + -> CellMembrane + Ask what happens when a box-cell goes wrong #opt-wrong + -> CellGoneWrong + +beat ExploreNucleus + You approach the nucleus. Its double membrane is like a box within a box. Security through redundant boxing. #nucleus-approach + cellina: The nuclear envelope. Two membranes. It's a box with a backup box. #cellina-envelope + Inside, chromosomes are neatly arranged. They look like instruction manuals, tightly folded. #nucleus-chromosomes + mike: Each chromosome is a chapter in the instruction book for building a human. #mike-chapter + mike: And what is a human? A very large, very complicated, self-moving box. #mike-human + cellina: I can't even argue with that. We're bipedal boxes filled with smaller boxes. #cellina-bipedal + sciencePoints += 15 + -> CellMembrane + +beat CellMembrane + You swim to the cell membrane -- the outer wall of this biological box. #membrane-swim + cellina: The membrane is selectively permeable. It decides what goes in and out. #cellina-permeable + cellina: It's a box that chooses who gets to open it. The most security-conscious box in nature. #cellina-security + mike: Ion channels are the locks. Receptor proteins are the keys. #mike-channels + mike: And sometimes, the box lets in a virus by accident because the virus has a fake key. #mike-virus + mike: Box security isn't perfect. Nothing is. Not even boxes. #mike-imperfect + sciencePoints += 10 + -> VirusAlert + +beat CellGoneWrong + cellina: When a cell goes wrong? That's when the box breaks. #cellina-breaks + cellina: Cancer, for instance. That's a box that forgot how to stop copying itself. #cellina-cancer + cellina: It just keeps making boxes. Boxes on boxes on boxes. No quality control. #cellina-quality + mike: It's a production line with no off switch. The box equivalent of reply-all. #mike-replyall + cellina: That's... a surprisingly apt analogy, Mike. #cellina-apt + sciencePoints += 10 + -> VirusAlert + +beat VirusAlert + An alarm sounds. Or rather, the cell starts vibrating in a concerning way. #virus-alarm + cellina: Oh no. The cell is under attack! #cellina-attack + mike: VIRUS! A virus is trying to get in! It's disguised as a delivery box! #mike-virus-alert + mike: This is why I have trust issues! Everything LOOKS like a legitimate box but some boxes are LIARS! #mike-trust + A virus particle -- tiny, geometric, and menacing -- latches onto the membrane. #virus-latch + cellina: It's trying to inject its contents. It's an evil box delivering evil instructions! #cellina-evil + + choice + Help the cell fight the virus #opt-fight + -> FightVirus + Observe the immune response scientifically #opt-observe + sciencePoints += 15 + -> ObserveResponse + Ask Mike to do something #opt-ask-mike + mikeHappiness += 10 + -> MikeToTheRescue + +beat FightVirus + You grab a nearby antibody -- it's shaped like a Y, which is just a box with arms -- and hurl it at the virus. #fight-hurl + cellina: That's not how immunology works, but I appreciate the enthusiasm! #cellina-immunology + The antibody latches onto the virus, neutralizing it. The membrane holds. #fight-latches + mike: Box integrity maintained! The cell is safe! #mike-safe + sciencePoints += 10 + -> GrandRealization + +beat ObserveResponse + You watch as the cell's defense mechanisms activate. White blood cells approach from outside. #observe-defense + cellina: Macrophages! The immune system's cleanup crew! They're essentially boxes that eat other boxes! #cellina-macrophages + A macrophage engulfs the virus. It's like watching a box swallow a smaller box. #observe-engulf + cellina: Phagocytosis. A box consuming a box. It's boxes all the way down, even in immunology. #cellina-phagocytosis + sciencePoints += 20 + -> GrandRealization + +beat MikeToTheRescue + mike: You want ME to fight the virus? I'm a MITOCHONDRION! I make energy! I don't DO combat! #mike-combat + mike: But you know what? Fine! I'll generate so much ATP that this cell will be INVINCIBLE! #mike-invincible + Mike starts glowing. Brighter. BRIGHTER. The cell fills with energy. #mike-glowing + cellina: Mike is over-producing ATP! The cell has so much energy it's... vibrating the virus off! #cellina-vibrating + The virus detaches and floats away, defeated by sheer metabolic enthusiasm. #mike-defeated + mikeHappiness += 30 + mike: That's right! Nobody messes with the Box Opener of the Cell! #mike-nobody + -> GrandRealization + +beat GrandRealization + The crisis passes. The cell calms down. You float in the warm cytoplasm, thinking. #realization-float + cellina: You know, I started this research to understand cells. But what I've really learned is about boxes. #cellina-learned + cellina: Every cell is a box. Every organelle is a box within a box. #cellina-every + cellina: Life itself is just... boxes organizing other boxes to make more boxes. #cellina-life + mike: And at the center of every box, there's a smaller box trying to be taken seriously. #mike-center + cellina: Is that a metaphor, Mike? #cellina-metaphor + mike: Everything is a metaphor when you're a sentient organelle having a conversation inside a human body. #mike-everything + sciencePoints += 10 + + if rebranded + mike: But at least I'm not "the powerhouse" anymore. I'm the Box Opener. And I'm proud. #mike-proud + mike: When we get back to normal size, I want business cards. Tiny, tiny business cards. #mike-cards + + -> Ending + +beat Ending + cellina: Time to return to normal size. Initiating expansion sequence. #ending-expand + The world blurs, shrinks, and then snaps back to normal. You're in the lab. Full-sized. Still wearing the lab coat. #ending-lab + cellina: Science points earned: $sciencePoints. Cells explored: $cellsExplored. #cellina-stats + cellina: Mike happiness level: $mikeHappiness. #cellina-mike + + if mikeHappiness > 60 + cellina: Mike seems happy. For a mitochondrion. His ATP output has increased 40%. #cellina-happy + cellina: Turns out, organelle morale affects cellular performance. I should publish that. #cellina-publish + else + cellina: Mike is still grumpy. But he's always been grumpy. It's part of his charm. #cellina-grumpy + + cellina: You know what I've learned today? #cellina-today + cellina: Biology isn't about cells, or DNA, or evolution. #cellina-biology + cellina: It's about boxes. Tiny, beautiful, impossibly complex boxes that somehow became alive. #cellina-boxes + cellina: And one of those boxes really, really wants a new nickname. #cellina-nickname + -> . diff --git a/content/adventures/pirate/intro.lor b/content/adventures/pirate/intro.lor new file mode 100644 index 0000000..51a5162 --- /dev/null +++ b/content/adventures/pirate/intro.lor @@ -0,0 +1,277 @@ +// Pirate Adventure - Open The Box +// Theme: High seas treasure hunting, but the treasure is always boxes + +character captain + name: Blackbeard the Unboxable + role: Pirate Captain + +character mate + name: Linu + role: First Mate + +state + doubloons: 30 + boxesFound: 0 + crewMorale: 50 + hasMap: false + betrayed: false + +beat Intro + The salty wind hits your face as you stand on the deck of The Corrugated Corsair. #intro-wind + captain: Arrr! Welcome aboard, ye scurvy landlubber! #captain-welcome + captain: I be Blackbeard the Unboxable! They call me that because no box can contain me! #captain-name + mate: They call him that because he can't figure out how to open boxes, Captain. #mate-truth + captain: LINU! What have I told ye about telling the truth?! #captain-truth + mate: That it's "mutiny with extra steps," Captain. #mate-mutiny + + choice + Ask about the mission #opt-mission + -> MissionBrief + Ask why the ship is made of cardboard #opt-cardboard + -> CardboardShip + Attempt to leave immediately #opt-leave + -> CantLeave + +beat CardboardShip + captain: The Corrugated Corsair is made from the finest triple-wall cardboard! #captain-corsair + captain: She's waterproof! Mostly. Don't touch the starboard side. Or breathe on it. #captain-waterproof + mate: We've lost three deckhands to structural dampness this week. #mate-dampness + captain: They knew the risks when they signed up for a cardboard pirate ship. #captain-risks + -> MissionBrief + +beat CantLeave + You turn toward the gangplank. It's gone. It was also made of cardboard. A seagull ate it. #leave-gangplank + captain: Ha! No one leaves Blackbeard's crew! Mostly because the exit dissolved! #captain-noleave + mate: We really should switch to wood, Captain. #mate-wood + captain: NEVER! Cardboard or death! #captain-death + -> MissionBrief + +beat MissionBrief + captain: We be searching for the legendary Treasure Box of the Seven Seas! #captain-treasure + captain: A box so valuable, it contains ALL other boxes! The mother of all boxes! #captain-mother + mate: Supposedly. The map was found inside a box, so its reliability is questionable. #mate-map + captain: Every map is found inside SOMETHING, Linu! That's how maps work! #captain-maps + hasMap = true + The captain unfurls a map. It's just a drawing of a box with an X on it. #mission-unfurl + + choice + Follow the map as-is #opt-follow + -> FollowMap + Suggest the map might be upside down #opt-upside + -> UpsideDown + Question whether a box drawing constitutes a map #opt-question + -> QuestionMap + +beat QuestionMap + captain: Of course it's a map! It has an X! #captain-x + mate: Captain, you drew the X yourself. Five minutes ago. In crayon. #mate-crayon + captain: It was TACTICAL crayon, Linu! #captain-crayon + mate: There's no such thing as tactical crayon. #mate-tactical + captain: There is now! I just invented it! Innovation, Linu! #captain-innovation + crewMorale -= 10 + -> FollowMap + +beat UpsideDown + You rotate the map 180 degrees. It looks exactly the same because it's just a square. #upside-rotate + captain: By Davy Jones' Box Collection! The map was upside down the whole time! #captain-jones + mate: Captain, it's a square. It looks the same from every angle. #mate-square + captain: That's what makes it such a CLEVER map! #captain-clever + crewMorale += 10 + -> FollowMap + +beat FollowMap + The Corrugated Corsair sets sail across the Cardboard Sea. #follow-sail + After two days of sailing, you spot something on the horizon. #follow-horizon + mate: Captain! Land ho! Or... box ho? It appears to be a floating box. #mate-landho + captain: A floating box in the ocean? That's either our treasure or our lunch. #captain-floating + captain: The crew has been eating boxes for days. We're running low on provisions. #captain-provisions + mate: We're running low on ship, too. Someone ate part of the hull. #mate-hull + + choice + Sail toward the floating box #opt-sail-toward + -> FloatingBox + Fire a cannon at it first #opt-cannon if doubloons > 10 + doubloons -= 10 + -> FireCannon + Send Linu to investigate in a rowboat #opt-send-linu + -> LinuInvestigates + +beat FireCannon + captain: FIRE THE CARDBOARD CANNONS! #captain-fire + The cannon makes a "pffft" sound and launches a cardboard ball approximately twelve feet. #cannon-pffft + It lands in the water with a soft, disappointing splash. #cannon-splash + mate: Effective range: twelve feet. Effective damage: emotional. #mate-range + captain: The enemy doesn't know our cannons are useless! That's our advantage! #captain-advantage + -> FloatingBox + +beat LinuInvestigates + Linu rows out to the box in a tiny boat made of -- you guessed it -- cardboard. #linu-rows + mate: It's a crate, Captain! Marked "Property of the Seven Seas Box Co." #linu-crate + mate: Also, my boat is dissolving. Permission to hurry? #linu-dissolving + captain: Permission granted! Save the box first, though! #captain-savebox + mate: Before me?! #linu-before + captain: Priorities, Linu! #captain-priorities + boxesFound += 1 + -> FloatingBox + +beat FloatingBox + You pull alongside the mysterious floating box. It's enormous. Waterlogged but intact. #floating-pull + captain: This be it! The Treasure Box of the Seven Seas! #captain-thisbe + mate: How can you tell? #linu-how + captain: It says "Treasure Box of the Seven Seas" on the side. #captain-says + mate: Oh. That IS convenient. #linu-convenient + boxesFound += 1 + + choice + Open the treasure box immediately #opt-open-treasure + -> OpenTreasure + Check for traps first #opt-check-traps + -> CheckTraps + Claim the box without opening it -- it's worth more sealed #opt-sealed + -> SealedBox + +beat CheckTraps + You examine the box carefully. There's a small warning label. #traps-examine + The label reads: "WARNING: Contents may contain more boxes. And also a kraken." #traps-label + mate: A kraken? In a box? #linu-kraken + captain: That's how they ship 'em these days. Free-range krakens are expensive. #captain-kraken + crewMorale -= 10 + -> OpenTreasure + +beat SealedBox + captain: Brilliant! A sealed box is worth ten opened ones! That's pirate economics! #captain-economics + mate: That's not how economics works. #linu-economics + captain: What are ye, a pirate or an accountant? #captain-accountant + mate: Currently? An accountant. Someone has to manage our negative doubloon balance. #linu-accountant + You secure the sealed box in the cargo hold. It rattles suspiciously. #sealed-rattles + -> RivalPirates + +beat OpenTreasure + You pry open the massive treasure box. Inside, there are... #open-pry + captain: MORE BOXES! #captain-moreboxes + Indeed. Hundreds of smaller boxes, stacked neatly. Each labeled with a different sea. #open-hundreds + mate: "Adriatic Box." "Caribbean Box." "That Puddle Behind Gary's House Box." #linu-labels + captain: Seven seas, hundreds of boxes. The math checks out. Pirate math, anyway. #captain-math + boxesFound += 3 + + choice + Open all the smaller boxes #opt-open-all + -> OpenAllBoxes + Take them back to port to sell #opt-sell + doubloons += 50 + -> RivalPirates + Build a raft out of the boxes since the ship is sinking #opt-raft if crewMorale < 40 + -> BoxRaft + +beat OpenAllBoxes + You spend the next four hours opening boxes. Your hands are covered in cardboard cuts. #openall-hours + Each box contains a smaller box. It's boxes all the way down. #openall-alltheway + captain: We're rich! Rich in BOXES! #captain-rich + mate: We can't spend boxes, Captain. #linu-spend + captain: Not with THAT attitude! #captain-attitude + At the very bottom of the last box, you find a single gold doubloon and a note. #openall-note + The note reads: "IOU - One Treasure. Sorry, the kraken needed it." #openall-iou + doubloons += 1 + -> RivalPirates + +beat BoxRaft + The ship is taking on water. Specifically, it's becoming water, because it's cardboard. #raft-water + mate: Captain, the ship is dissolving! #linu-ship + captain: Then we BUILD! From the treasure boxes! A new ship! A BOX SHIP! #captain-build + mate: As opposed to our current box ship? #linu-current + captain: A BETTER box ship! With LAMINATION! #captain-lamination + You construct a surprisingly seaworthy raft from the treasure boxes. #raft-construct + crewMorale += 20 + -> RivalPirates + +beat RivalPirates + A ship appears on the horizon. A WOODEN ship. How luxurious. #rival-appears + captain: Rival pirates! It's Captain Unboxinator and his crew! #captain-rival + mate: They have real cannons, Captain. And a ship that doesn't dissolve. #linu-real + captain: Details! #captain-details + The rival ship pulls alongside. Their captain, a large woman with a crowbar for a hand, grins. #rival-pulls + She shouts across the water: "Hand over your boxes, Blackbeard!" #rival-shouts + + choice + Fight the rival pirates #opt-fight + -> SeaBattle + Negotiate a trade #opt-negotiate if doubloons > 20 + doubloons -= 20 + -> Negotiate + Challenge them to a box-opening race #opt-race + -> BoxRace + +beat SeaBattle + captain: Battle stations! Load the cardboard cannons! #captain-stations + mate: Captain, they're laughing at us. #linu-laughing + captain: Good! Laughter is the enemy of focus! While they laugh, we... throw boxes at them! #captain-throw + You hurl boxes at the rival ship. One hits their captain in the face. #battle-hurl + She's momentarily stunned. Not by the impact -- by the audacity. #battle-stunned + boxesFound += 1 + crewMorale += 20 + -> TwistEnding + +beat Negotiate + captain: Perhaps we can reach a... box-ness arrangement? #captain-arrangement + The rival captain narrows her eyes. She considers. #negotiate-considers + She responds: "Half your boxes. And that weird map you drew in crayon." #rival-half + captain: You can have the boxes. But the crayon map is MY intellectual property! #captain-ip + mate: Let it go, Captain. #linu-letitgo + boxesFound -= 1 + -> TwistEnding + +beat BoxRace + captain: A box-opening race! First crew to open fifty boxes wins! #captain-race + The rival captain agrees. She's confident. Her crew has real tools. #race-agrees + But your crew has DESPERATION. And cardboard cuts have made your fingers incredibly nimble. #race-desperation + The race begins. Boxes fly open left and right. Cardboard confetti fills the air. #race-begins + You win by a single box. The last box contains a rubber duck. Nobody knows why. #race-duck + boxesFound += 5 + crewMorale += 30 + -> TwistEnding + +beat TwistEnding + As the rival pirates retreat (or celebrate, depending on the outcome), Linu pulls you aside. #twist-aside + mate: Look at the bottom of the treasure box, Captain. There's a hidden compartment. #linu-hidden + You open the hidden compartment. Inside: a single, perfect, golden box. #twist-golden + captain: The REAL Treasure Box of the Seven Seas! #captain-realbox + mate: What's inside it? #linu-inside + captain: Linu, I've been a pirate for thirty years. I've opened ten thousand boxes. #captain-thirty + captain: And I've learned one thing: it's never about what's inside the box. #captain-lesson + + choice + Open the golden box anyway #opt-open-golden + -> FinalReveal + Keep it sealed forever #opt-keep-sealed + -> KeepSealed + +beat FinalReveal + You open the golden box. Inside, there's a mirror. #reveal-mirror + You see your own reflection staring back at you. #reveal-reflection + captain: The real treasure... was us? #captain-us + mate: No, Captain. It's literally just a mirror. Someone put a mirror in a box. #linu-mirror + captain: A METAPHORICAL mirror, Linu! #captain-metaphor + mate: It's a regular mirror. I can see the price tag. It cost three doubloons. #linu-pricetag + captain: The cheapest treasures are the most priceless! #captain-priceless + mate: That's... not even slightly true. #linu-nottrue + -> Ending + +beat KeepSealed + captain: This box shall remain sealed! For all eternity! Or until someone gets curious! #captain-sealed + mate: So... about five minutes, then? #linu-fiveminutes + captain: Probably less. I'm already curious. #captain-curious + captain: But a good pirate knows that some boxes are better left unopened. #captain-good + captain: And I am a terrible pirate. So I'll open it Tuesday. #captain-tuesday + -> Ending + +beat Ending + The sun sets over the Cardboard Sea. Your adventure draws to a close. #ending-sunset + captain: We found boxes! We opened boxes! We ARE boxes! #captain-summary + mate: We're not boxes, Captain. #linu-notboxes + captain: Speak for yourself, Linu. I've always identified as a shipping container. #captain-container + mate: Boxes found today: $boxesFound. Ship structural integrity: questionable. #linu-log + mate: Doubloons remaining: $doubloons. Captain's sanity: also questionable. #linu-sanity + captain: Set course for the next adventure, Linu! There are always more boxes on the horizon! #captain-next + mate: The horizon is sinking, Captain. Along with our ship. #linu-sinking + captain: Then we'll sink WITH STYLE! #captain-style + -> . diff --git a/content/adventures/prehistoric/intro.lor b/content/adventures/prehistoric/intro.lor new file mode 100644 index 0000000..c811146 --- /dev/null +++ b/content/adventures/prehistoric/intro.lor @@ -0,0 +1,218 @@ +// Prehistoric Adventure - Open The Box +// Theme: The first box in history appears to very confused cavpeople + +character grug + name: Grug + role: Caveperson + +character duncan + name: Duncan + role: Another Caveperson + +state + understanding: 0 + boxDamage: 0 + worshippers: 0 + inventedOpening: false + +beat Intro + The year is approximately a very long time ago. The sun is orange. The ground is dirt. Everything smells like mammoth. #intro-year + You're sitting in your cave, doing cave things. Drawing boxes on the wall. Wait, no. Drawing mammoths. Boxes haven't been invented yet. #intro-cave + grug: Grug see thing. #grug-see + grug: Thing not rock. Thing not tree. Thing not mammoth. Grug confused. #grug-confused + A perfect cardboard box sits in the middle of the clearing. It should not exist. Cardboard won't be invented for millennia. #intro-cardboard + duncan: Duncan also see thing. Duncan poke thing with stick? #duncan-poke + + choice + Poke the thing with a stick #opt-poke + -> PokeWithStick + Hit the thing with a rock #opt-rock + boxDamage += 10 + -> HitWithRock + Try to eat the thing #opt-eat + -> TryToEat + +beat PokeWithStick + You poke the box with a stick. The box does nothing. It's a box. #poke-nothing + grug: Thing not fight back. Thing weak. Grug not impressed. #grug-weak + duncan: Maybe thing sleeping? Duncan poke harder? #duncan-harder + You poke harder. The stick goes through the cardboard and gets stuck. #poke-stuck + grug: Stick gone! Thing ATE stick! #grug-ate + duncan: Thing is monster! A flat, square monster! #duncan-monster + understanding += 5 + -> FirstReactions + +beat HitWithRock + You pick up the biggest rock you can find and hurl it at the box. #rock-hurl + The box dents. The rock bounces off. #rock-dent + grug: Grug victory! Thing is defeated! #grug-victory + duncan: Thing still there, Grug. #duncan-still + grug: Grug claim MORAL victory! #grug-moral + boxDamage += 20 + understanding += 5 + -> FirstReactions + +beat TryToEat + You bite the box. It tastes like cardboard, which your brain has no reference for, so it files it under "bad tree." #eat-bite + grug: Thing taste like bad tree bark! Zero out of five mammoth tusks! #grug-taste + duncan: Duncan not surprised. Thing LOOK like bad tree bark. #duncan-notsurprised + duncan: Flat bad tree bark in shape of... what shape is that? #duncan-shape + grug: New shape. Grug call it... "box." #grug-name + You have accidentally invented the word "box" three hundred thousand years early. #eat-invented + understanding += 15 + -> FirstReactions + +beat FirstReactions + The other cave people have gathered around the mysterious box. #reactions-gather + grug: Big meeting! Everyone look at thing! #grug-meeting + duncan: Some cave people scared. Some cave people hungry. Carl tried to mate with it. We don't talk about Carl. #duncan-carl + worshippers += 2 + + choice + Suggest worshipping the box #opt-worship + worshippers += 10 + -> WorshipBox + Suggest using the box as a shelter #opt-shelter + -> BoxShelter + Try to understand the box through interpretive dance #opt-dance + understanding += 10 + -> InterpretiveDance + +beat WorshipBox + The tribe gathers around the box and begins chanting. The chant is "BOX. BOX. BOX." #worship-chant + grug: Box is gift from sky! Sky give box! We give sky... what we give sky? #grug-sky + duncan: Rocks? We have many rocks. #duncan-rocks + grug: Rocks boring. Sky already HAVE rocks. Moon is just sky-rock. #grug-moon + You build a small altar around the box. The altar is made of smaller rocks arranged in a square. You've accidentally invented the concept of a pedestal. #worship-altar + worshippers += 5 + understanding += 5 + -> TheFlaps + +beat BoxShelter + grug: Thing is hollow! Grug see inside through hole stick made! #grug-hollow + duncan: If thing is hollow, cave person fit inside! New cave! PORTABLE cave! #duncan-portable + The tribe tries to fit inside the box. It's a standard-sized box. The tribe is twelve full-grown cave people. #shelter-fit + grug: Only Grug's arm fits. This worst cave ever. #grug-arm + duncan: Maybe not cave. Maybe hat? Very big hat? #duncan-hat + You put the box on your head. It fits perfectly. You are now wearing history's first hat and you look ridiculous. #shelter-hat + understanding += 10 + -> TheFlaps + +beat InterpretiveDance + You attempt to communicate with the box through movement. #dance-attempt + Your dance involves a lot of squatting and arm-waving. The box remains unimpressed. #dance-squatting + grug: What you doing? You look like mammoth having a bad dream. #grug-mammoth + duncan: No wait. Duncan think there's something to this. #duncan-think + Duncan joins the dance. His technique is worse. Together, you look like two mammoths having bad dreams. #dance-worse + The box... vibrates slightly? Or that might be an earthquake. It's hard to tell in prehistoric times. #dance-vibrate + understanding += 10 + -> TheFlaps + +beat TheFlaps + grug: Wait. Grug notice something. Top of thing has... floppy parts. #grug-floppy + duncan: Floppy parts? Like mammoth ears? #duncan-ears + grug: No. Different floppy. These floppy parts FOLD. They go up. They go down. #grug-fold + grug: They go up AND down. This most advanced technology Grug ever see. #grug-technology + understanding += 15 + The tribe stares at the flaps with collective wonder. Folding things is a brand new concept. #flaps-wonder + + choice + Pull the flaps up #opt-pull-up + understanding += 20 + -> AlmostOpen + Push the flaps down #opt-push-down + -> PushDown + Declare the flaps sacred and forbid touching them #opt-sacred + worshippers += 10 + -> SacredFlaps + +beat PushDown + You push the flaps down. They're already down. Nothing happens. #push-nothing + grug: Congratulations. You have done nothing. Very efficiently. #grug-nothing + duncan: Maybe pull UP? What is "up"? Is "up" a thing? #duncan-up + grug: "Up" is where birds go. And smoke. And Grug's hopes when mammoth hunt fails. #grug-up + understanding += 10 + -> AlmostOpen + +beat SacredFlaps + grug: NO TOUCH FLOPPY PARTS! Floppy parts are gift from sky! #grug-notouch + duncan: But what if floppy parts WANT to be touched? #duncan-want + grug: Duncan asking dangerous questions. That how you get exiled. #grug-exiled + worshippers += 5 + The tribe guards the flaps for three days. On the fourth day, a bird lands on the box and accidentally opens one flap with its foot. #sacred-bird + grug: BIRD IS CHOSEN ONE! #grug-bird + The bird flies away, unaware of its religious significance. #sacred-flies + understanding += 15 + -> AlmostOpen + +beat AlmostOpen + You stand before the box. The flaps are partially up. You can see inside, but not fully. #almost-see + grug: Something INSIDE thing! Something inside the inside! #grug-inside + duncan: "Inside." That new word. Grug keep inventing words today. #duncan-word + grug: Grug not inventing. Words just HAPPENING to Grug. #grug-happening + + choice + Pull all flaps up to fully reveal the inside #opt-reveal + inventedOpening = true + understanding += 30 + -> OpenTheBox + Get the whole tribe to pull one flap each #opt-teamwork + inventedOpening = true + understanding += 30 + -> TeamOpen + +beat TeamOpen + Four tribe members each grab a flap. You count to three. #team-count + grug: What "three"? We only have "one" and "many." #grug-three + Fine. You count to "many." #team-many + On "many," everyone pulls. The box opens with a satisfying "fwump." #team-fwump + grug: THE THING IS OPEN! WE DID A THING TO THE THING! #grug-open + duncan: This is the greatest achievement in the history of... what's "history"? #duncan-history + -> BoxContents + +beat OpenTheBox + You pull the flaps up. All of them. The box is open. #open-flaps + grug: Grug has done it. Grug has... OPENED... the... thing. #grug-done + grug: "Opened." Another new word. Grug on a roll. #grug-roll + duncan: What "roll"? #duncan-roll + grug: Grug don't know. Words just keep coming. It's frightening. #grug-frightening + -> BoxContents + +beat BoxContents + The tribe peers inside the open box. #contents-peer + Inside: another, smaller box. #contents-smaller + grug: ... #grug-silence + duncan: ... #duncan-silence + grug: THERE IS A THING INSIDE THE THING! #grug-thingception + duncan: A smaller thing! Same shape! This is the most beautiful and confusing moment of Grug's life! #duncan-beautiful + The smaller box also has flaps. The tribe loses its collective mind. #contents-flaps + + if inventedOpening + You open the smaller box. Inside: a single, perfectly round stone. #contents-stone + grug: A round rock! In a square thing! Shapes are INCREDIBLE! #grug-shapes + duncan: What do we do with round rock? #duncan-roundrock + grug: We put it in another box. Obviously. #grug-obviously + duncan: We don't HAVE another box. #duncan-nobox + grug: Then we MAKE one! With... flat tree bark! And... folding! #grug-make + You have accidentally invented the concept of manufacturing. The industrial revolution just moved up by several millennia. #contents-manufacturing + else + The tribe stares at the box-within-a-box in silent awe. Some things are beyond opening. #contents-awe + grug: Grug will figure this out. Give Grug... many sleeps. #grug-sleeps + + -> Ending + +beat Ending + The sun sets over the prehistoric landscape. The box sits in the center of the tribe's camp, flaps open, glowing in the firelight. #ending-sun + grug: Today, Grug learn new things. "Box." "Open." "Inside." "Flaps." #grug-learned + grug: Tomorrow, Grug learn more. Maybe "lid." Maybe "tape." Grug ambitious. #grug-tomorrow + duncan: Duncan just happy Duncan didn't get eaten today. Low bar, but Duncan clear it. #duncan-happy + + if worshippers > 10 + The Box Religion has begun. It will outlast several ice ages and confuse many future archaeologists. #ending-religion + + grug: One day, Grug's children's children's children will open MANY boxes. #grug-children + grug: And they will think: "Some cave person started this. Some cave person was the first." #grug-first + grug: And that cave person was Grug. Unless it was Duncan. Grug not great with credit. #grug-credit + duncan: It was definitely Grug. Duncan was just here for emotional support. #duncan-credit + grug: Understanding level: $understanding. Box damage: $boxDamage. Words invented: too many. #grug-stats + -> . diff --git a/content/adventures/sentimental/intro.lor b/content/adventures/sentimental/intro.lor new file mode 100644 index 0000000..dfa41ce --- /dev/null +++ b/content/adventures/sentimental/intro.lor @@ -0,0 +1,197 @@ +// Sentimental Adventure - Open The Box +// Theme: A box of memories, love, and bittersweet nostalgia + +character chenda + name: Chenda + role: Love Interest + +character farah + name: Farah + role: Best Friend + +state + memories: 0 + heartLevel: 50 + chendaClose: false + acceptedPast: false + +beat Intro + Rain taps against the attic window. You're surrounded by dust and the quiet ghosts of years gone by. #intro-rain + In the corner, half-hidden under a moth-eaten blanket, you find it: a box. #intro-find + It's not special-looking. Brown cardboard. A little crushed on one side. Held together with tape that's given up on being sticky. #intro-box + But you recognize the handwriting on the side. It says "Us." #intro-us + farah: Hey, I thought I'd find you up here. What's that? #farah-find + farah: Oh. That box. I remember that box. #farah-remember + + choice + Open the box #opt-open + -> OpenMemoryBox + Ask Farah what she remembers #opt-ask-farah + -> FarahRemembers + Put it back -- some boxes should stay closed #opt-putback + -> PutItBack + +beat FarahRemembers + farah: That's the box from the summer Chenda moved in next door. #farah-summer + farah: You two used to pass notes in it. Back and forth over the fence. #farah-notes + farah: You called it "Box Mail." You were twelve. It was adorable and deeply uncool. #farah-boxmail + farah: Chenda still has the other box. The one you sent your notes IN. #farah-otherbox + heartLevel += 10 + -> OpenMemoryBox + +beat PutItBack + You push the box back under the blanket. #putback-push + farah: You sure? #farah-sure + A moment passes. The rain gets louder. #putback-rain + farah: You know, I read somewhere that unopened boxes are just feelings with a lid on them. #farah-feelings + farah: I didn't read that anywhere. I just made it up. But it sounded wise, right? #farah-wise + You look at the box again. Maybe some feelings deserve to be unlidded. #putback-unlid + -> OpenMemoryBox + +beat OpenMemoryBox + You open the box carefully. The flaps resist, then give way with a soft sigh. #open-careful + Inside: a mess of trinkets, notes, and small objects that meant everything once. #open-inside + memories += 1 + farah: Oh wow. It's all still here. #farah-wow + + choice + Pick up the photograph on top #opt-photo + -> MemoryPhoto + Read the folded note #opt-note + -> MemoryNote + Look at the small wooden figurine #opt-figurine + -> MemoryFigurine + +beat MemoryPhoto + It's a polaroid. Slightly faded. Two teenagers standing in front of a moving truck. #photo-polaroid + One is you. The other is Chenda, holding up a cardboard box and grinning like it's a trophy. #photo-chenda + The day Chenda moved in. The day everything started. #photo-day + farah: You two became friends because Chenda's parents needed help carrying boxes. #farah-carrying + farah: Literally. Your entire relationship started because of cardboard boxes. #farah-cardboard + farah: If that's not fate, I don't know what is. #farah-fate + heartLevel += 15 + memories += 1 + -> MemoryNote + +beat MemoryNote + You unfold the note. The paper is soft from years of being folded and unfolded. #note-unfold + It reads, in Chenda's handwriting: "Thanks for helping with the boxes yesterday. You're weird, but the good kind of weird. Box Mail is the best invention ever. - C" #note-reads + heartLevel += 20 + farah: The "good kind of weird." That's the nicest thing anyone's ever said about you. #farah-weird + farah: And she wasn't wrong. About either part. #farah-notWrong + memories += 1 + + choice + Keep reading through the box #opt-keep-reading + -> MemoryFigurine + Call Chenda #opt-call + chendaClose = true + -> CallChenda + +beat MemoryFigurine + At the bottom of the box: a small wooden figurine. A box. Carved from a popsicle stick. #figurine-bottom + You remember making it in shop class. You gave it to Chenda as a joke. #figurine-joke + "A box for someone who changed my life with a box." You'd written it on a gift tag. #figurine-tag + farah: You were so smooth. Like sandpaper, but smoother. #farah-smooth + farah: I think Chenda kept the gift tag too. I saw it on the fridge last week. #farah-fridge + heartLevel += 20 + memories += 1 + + choice + Call Chenda now #opt-call-now + chendaClose = true + -> CallChenda + Sit with the memories a while longer #opt-sit + -> SitWithMemories + +beat SitWithMemories + You sit on the attic floor, the box in your lap, rain on the window. #sit-floor + farah: You know what the best thing about boxes is? #farah-best + farah: They keep things safe. Even when you forget about them. Even when years pass. #farah-safe + farah: The stuff inside doesn't change. It just waits for you to come back. #farah-waits + heartLevel += 10 + You realize she's not just talking about boxes. #sit-realize + -> CallChenda + +beat CallChenda + You pick up your phone. Chenda's number is still there. Of course it is. #call-phone + It rings three times. Then: #call-rings + chenda: Hello? Oh! Hey! I was just... you're not going to believe this. #chenda-hello + chenda: I'm in MY attic. Looking at MY box. The one with your notes in it. #chenda-attic + heartLevel += 20 + farah: I'm going to go make tea and pretend this isn't making me emotional. #farah-tea + + choice + Tell Chenda you found the box too #opt-tell + -> SharedMoment + Ask if Chenda still has the wooden figurine #opt-figurine-ask + -> AskFigurine + Invite Chenda over #opt-invite + chendaClose = true + -> InviteOver + +beat AskFigurine + chenda: The little wooden box you made? Of course I still have it. #chenda-figurine + chenda: It's on my desk. Right next to my computer. I see it every day. #chenda-desk + chenda: I never told you that. That seems like the kind of thing I should have told you. #chenda-shouldve + heartLevel += 15 + -> SharedMoment + +beat InviteOver + chenda: I'll be there in twenty minutes. Keep the box open. I want to see everything. #chenda-coming + chenda: And don't eat the candy at the bottom. I know there's candy. Old candy, but still. #chenda-candy + farah: There IS candy at the bottom. It's been there for ten years. I would not eat it. #farah-candy + heartLevel += 10 + -> SharedMoment + +beat SharedMoment + chenda: You know, I was thinking about the day we met. #chenda-thinking + chenda: My parents were yelling about which box went where. I was miserable. New town, no friends. #chenda-miserable + chenda: And then you showed up. This random kid, offering to carry boxes. #chenda-random + chenda: You dropped the first one. It had all my books in it. They went everywhere. #chenda-dropped + chenda: And you looked so mortified that I started laughing. And then you laughed. #chenda-laughed + chenda: And I thought: okay. Maybe this place won't be so bad. #chenda-okay + heartLevel += 25 + memories += 1 + + choice + Say "I still can't carry boxes properly" #opt-still-cant + -> StillCant + Say "That was the best box I ever dropped" #opt-best-drop + -> BestDrop + +beat StillCant + chenda: I know. I've seen you try. It's a miracle you function as an adult. #chenda-function + chenda: But you always show up. Even when you'll definitely drop the box. #chenda-showup + chenda: That's the thing about you. You show up. #chenda-always + acceptedPast = true + -> Ending + +beat BestDrop + chenda: Only you would romanticize dropping someone's belongings on the ground. #chenda-romanticize + chenda: But yeah. It was a pretty good drop. A+ dropping. #chenda-aplus + chenda: Every time I see a cardboard box now, I think of you. Which is inconvenient. #chenda-inconvenient + chenda: Boxes are EVERYWHERE. I can't go to the post office without getting nostalgic. #chenda-postoffice + acceptedPast = true + -> Ending + +beat Ending + The rain slows to a drizzle. The attic feels warmer than it did an hour ago. #ending-rain + You look at the box. Just a box. Cardboard, tape, scribbled handwriting. #ending-look + But it held everything that mattered. It kept it safe while you were busy forgetting. #ending-held + + if chendaClose + chenda: Hey. Let's not wait another ten years before we open a box together. #chenda-together + chenda: Deal? #chenda-deal + heartLevel += 20 + + farah: You two are disgustingly sweet. I mean that as a compliment. Mostly. #farah-sweet + farah: Memories found today: $memories. Heart level: $heartLevel. Tissues used by Farah: she's not saying. #farah-final + + if acceptedPast + You close the box gently. Not to seal it away again, but because the memories are safe now. #ending-close + They're not just in the box anymore. They're back where they belong. #ending-belong + + farah: For what it's worth, I'm glad you opened it. Some boxes really are worth opening. #farah-worth + farah: Even the scary ones. Especially the scary ones. #farah-especially + -> . diff --git a/content/adventures/space/intro.fr.lor b/content/adventures/space/intro.fr.lor new file mode 100644 index 0000000..b233e22 --- /dev/null +++ b/content/adventures/space/intro.fr.lor @@ -0,0 +1,299 @@ +// Space Adventure - Open The Box +// French Translation + +#intro-hum // "The ship hums quietly as you drift through sector 7-G." +Le vaisseau bourdonne doucement alors que vous derivez dans le secteur 7-G. + +#intro-scan // "Commander, scanners are detecting something unusual." +Commandant, les scanners detectent quelque chose d'inhabituel. + +#intro-box // "A box. Floating in space. Defying several laws of physics." +Une boite. Flottant dans l'espace. Defiant plusieurs lois de la physique. + +#captain-box // "A box? In space?" +Une boite ? Dans l'espace ? + +#opt-investigate // "Investigate the box" +Examiner la boite + +#opt-ignore // "Ignore it and continue" +L'ignorer et continuer + +#opt-scan // "Run a deep scan first" +Lancer un scan approfondi d'abord + +#deepscan-init // "Deep scan initiated. Fuel reserves reduced." +Scan approfondi lance. Reserves de carburant reduites. + +#deepscan-result // "Analysis complete. The box appears to be... sentient?" +Analyse terminee. La boite semble etre... sentiente ? + +#deepscan-humming // "It's humming a tune. I believe it's... the box theme." +Elle fredonne un air. Je crois que c'est... le theme de la boite. + +#captain-theme // "The box theme?" +Le theme de la boite ? + +#ai-theme // "Every box has a theme, Commander. Didn't you know?" +Chaque boite a un theme, Commandant. Vous ne le saviez pas ? + +#opt-open-sentient // "Open the sentient box" +Ouvrir la boite sentiente + +#opt-communicate // "Try to communicate with it" +Essayer de communiquer avec elle + +#opt-backaway // "Back away slowly" +Reculer doucement + +#investigate-approach // "You approach the box carefully. It's about a meter wide, floating serenely." +Vous approchez la boite prudemment. Elle fait environ un metre de large, flottant sereinement. + +#captain-beautiful // "It's... beautiful." +Elle est... magnifique. + +#ai-beautiful // "Commander, I wouldn't use that word for a box." +Commandant, je n'utiliserais pas ce mot pour une boite. + +#captain-words // "You don't tell me what words to use for boxes, ARIA." +Ne me dites pas quels mots utiliser pour les boites, ARIA. + +#opt-open-now // "Open it immediately" +L'ouvrir immediatement + +#opt-scan-first // "Scan it first" +La scanner d'abord + +#opt-poke // "Poke it with a stick" +La pousser avec un baton + +#poke-arm // "You extend the ship's robotic arm and gently poke the box." +Vous etendez le bras robotique du vaisseau et poussez delicatement la boite. + +#poke-label // "The box rotates 90 degrees and reveals a label: \"THIS SIDE UP\"" +La boite tourne de 90 degres et revele une etiquette : "CE COTE VERS LE HAUT" + +#ai-gravity // "Commander, I don't think boxes in zero gravity have an \"up\"." +Commandant, je ne pense pas que les boites en apesanteur aient un "haut". + +#captain-reality // "Maybe the box defines its own reality." +Peut-etre que la boite definit sa propre realite. + +#ai-philosophy // "That's... philosophically concerning." +C'est... philosophiquement inquietant. + +#scan-contents // "Scan reveals the box contains crystallized starlight and something called a \"Nebula Shard\"." +Le scan revele que la boite contient de la lumiere stellaire cristallisee et quelque chose appele un "Eclat de Nebuleuse". + +#scan-alien // "Also what appears to be a very small, very angry alien." +Ainsi que ce qui semble etre un extraterrestre tres petit et tres en colere. + +#captain-small // "Define \"very small\"." +Definissez "tres petit". + +#ai-alienbox // "Approximately the size of a box. Commander, I think the alien IS a box." +Approximativement la taille d'une boite. Commandant, je crois que l'extraterrestre EST une boite. + +#ignore-course // "You set course away from the mysterious box." +Vous mettez le cap loin de la boite mysterieuse. + +#ai-following // "Commander, the box is... following us." +Commandant, la boite nous... suit. + +#captain-what // "What?" +Quoi ? + +#ai-thrusters // "It appears to have tiny thrusters. And what I can only describe as determination." +Elle semble avoir de petits propulseurs. Et ce que je ne peux decrire que comme de la determination. + +#captain-determined // "A determined box. My favorite kind." +Une boite determinee. Mon genre prefere. + +#comm-channel // "ARIA, open a channel to the box." +ARIA, ouvrez un canal vers la boite. + +#ai-itsabox // "Commander, it's a box." +Commandant, c'est une boite. + +#captain-channel // "I said open a channel." +J'ai dit ouvrez un canal. + +#ai-channelopen // "Channel open, Commander." +Canal ouvert, Commandant. + +#captain-peace // "Hello, box. I am Captain Nova. I come in peace." +Bonjour, boite. Je suis le Capitaine Nova. Je viens en paix. + +#comm-vibrate // "The box vibrates gently. It seems pleased." +La boite vibre doucement. Elle semble satisfaite. + +#ai-friendship // "Commander, the box just sent us coordinates. And what appears to be... a friendship request?" +Commandant, la boite vient de nous envoyer des coordonnees. Et ce qui semble etre... une demande d'amitie ? + +#back-thrusters // "You engage reverse thrusters. The sentient box watches you leave." +Vous enclenchez les propulseurs inverses. La boite sentiente vous regarde partir. + +#ai-sad // "Commander, the box looks... sad." +Commandant, la boite a l'air... triste. + +#captain-feelings // "Boxes don't have feelings, ARIA." +Les boites n'ont pas de sentiments, ARIA. + +#ai-boxsadness // "This one does. I'm reading elevated levels of box-sadness." +Celle-ci en a. Je detecte des niveaux eleves de boite-tristesse. + +#captain-metric // "That's not a real metric." +Ce n'est pas une vraie mesure. + +#ai-level // "It is now. Box-sadness level: 7 out of 10." +Ca l'est maintenant. Niveau de boite-tristesse : 7 sur 10. + +#opt-goback // "Go back to the box" +Retourner vers la boite + +#opt-leave // "Leave for good" +Partir pour de bon + +#leave-behind // "You leave the sentient box behind." +Vous laissez la boite sentiente derriere vous. + +#ai-happy // "I hope you're happy, Commander." +J'espere que vous etes content, Commandant. + +#captain-happy // "I am. No weird space boxes for me today." +Je le suis. Pas de boites spatiales bizarres pour moi aujourd'hui. + +#ai-transmission // "Incoming transmission. It's... from the box." +Transmission entrante. C'est... de la boite. + +#ai-message // "It says \"you'll be back. they always come back.\"" +Elle dit "vous reviendrez. Ils reviennent toujours." + +#captain-ominous // "That's ominous. I love it." +C'est sinistre. J'adore. + +#open-light // "You carefully open the box. Light spills out from within." +Vous ouvrez prudemment la boite. De la lumiere se deverse de l'interieur. + +#open-friendly // "Inside you find a Nebula Shard, pulsing with friendly energy." +A l'interieur, vous trouvez un Eclat de Nebuleuse, pulsant d'une energie amicale. + +#alien-gift // "Ah, a fellow box appreciator! Take this as a gift from my collection." +Ah, un autre appreciateur de boites ! Prenez ceci comme cadeau de ma collection. + +#captain-wasbox // "The alien WAS the box?" +L'extraterrestre ETAIT la boite ? + +#alien-intro // "We prefer \"Box-Americans\". Just kidding. I'm Zx'thorp." +On prefere "Boite-Americains". Je plaisante. Je suis Zx'thorp. + +#open-normal // "Inside you find a Nebula Shard and what appears to be a map to more boxes." +A l'interieur, vous trouvez un Eclat de Nebuleuse et ce qui semble etre une carte vers d'autres boites. + +#ai-morebox // "Commander, the map shows three more floating boxes in nearby sectors." +Commandant, la carte indique trois autres boites flottantes dans les secteurs voisins. + +#captain-christmas // "Three more boxes. This is either Christmas or a trap." +Trois autres boites. C'est soit Noel, soit un piege. + +#ai-same // "In space, those are often the same thing." +Dans l'espace, c'est souvent la meme chose. + +#alien-collecting // "I've been collecting boxes across the galaxy for centuries." +Je collectionne des boites a travers la galaxie depuis des siecles. + +#alien-appreciate // "Most species open them. Very few appreciate them." +La plupart des especes les ouvrent. Tres peu les apprecient. + +#captain-loot // "I appreciate boxes. Especially ones with good loot." +J'apprecie les boites. Surtout celles avec du bon butin. + +#alien-crude // "\"Loot.\" What a crude word for cosmic treasures." +"Butin." Quel mot grossier pour des tresors cosmiques. + +#opt-rare-box // "Ask about the galaxy's rarest box" +Demander quelle est la boite la plus rare de la galaxie + +#alien-singularity // "The Singularity Box. It contains everything and nothing. Also a coupon." +La Boite Singuliere. Elle contient tout et rien. Et aussi un coupon. + +#opt-trade // "Trade items with Zx'thorp" +Echanger des objets avec Zx'thorp + +#alien-trade // "I have a Space Helmet for you. In exchange, I want your sense of wonder." +J'ai un Casque Spatial pour vous. En echange, je veux votre sens de l'emerveillement. + +#captain-deal // "Deal. Wait--" +Marche. Attendez-- + +#opt-contest // "Challenge the alien to a box-opening contest" +Defier l'extraterrestre dans un concours d'ouverture de boites + +#alien-dare // "You dare? No one out-opens Zx'thorp!" +Vous osez ? Personne ne surpasse Zx'thorp a l'ouverture ! + +#alien-contest // "...fine. Best of three. You open first." +...tres bien. Au meilleur des trois. Vous ouvrez en premier. + +#explore-course // "Setting course for the nearest box. ETA: approximately one dramatic pause." +Cap vers la boite la plus proche. Arrivee estimee : environ une pause dramatique. + +#explore-cluster // "You arrive at a cluster of three boxes, orbiting each other like a tiny solar system." +Vous arrivez a un groupe de trois boites, en orbite les unes autour des autres comme un minuscule systeme solaire. + +#captain-system // "A box system. Literally a system of boxes." +Un systeme de boites. Litteralement un systeme de boites. + +#ai-alltheway // "The large one appears to contain the other two. It's boxes all the way down." +La grande semble contenir les deux autres. Ce sont des boites jusqu'au bout. + +#opt-big // "Open the biggest box first" +Ouvrir la plus grande boite en premier + +#big-open // "You open the biggest box. Inside: two medium boxes and a note." +Vous ouvrez la plus grande boite. A l'interieur : deux boites moyennes et un mot. + +#big-note // "The note reads: \"Congratulations! You've found the Box Nebula. Population: boxes.\"" +Le mot dit : "Felicitations ! Vous avez trouve la Nebuleuse des Boites. Population : des boites." + +#opt-all // "Open all three at once" +Ouvrir les trois en meme temps + +#captain-velocity // "All three, ARIA. Maximum box velocity." +Les trois, ARIA. Vitesse maximale de boite. + +#ai-enthusiasm // "That's not a real measurement, but I admire your enthusiasm." +Ce n'est pas une vraie mesure, mais j'admire votre enthousiasme. + +#all-open // "All three boxes burst open simultaneously. The contents mix together in zero gravity." +Les trois boites s'ouvrent simultanement. Le contenu se melange en apesanteur. + +#ai-biggerbox // "Commander, the items are forming... a bigger box." +Commandant, les objets forment... une plus grande boite. + +#captain-ofcourse // "Of course they are." +Evidemment. + +#opt-happy // "Leave them orbiting, they seem happy" +Les laisser en orbite, elles ont l'air heureuses + +#captain-orbit // "Let them orbit in peace." +Laissons-les orbiter en paix. + +#ai-philosophical // "A surprisingly philosophical choice, Commander." +Un choix etonnamment philosophique, Commandant. + +#ai-happy // "The boxes seem to orbit faster. I think they're happy." +Les boites semblent orbiter plus vite. Je crois qu'elles sont heureuses. + +#ending-complete // "Adventure complete. Updating ship's log." +Aventure terminee. Mise a jour du journal de bord. + +#ending-log // "Boxes encountered: $discovered. Box-related existential crises: 1." +Boites rencontrees : $discovered. Crises existentielles liees aux boites : 1. + +#captain-next // "Only one? We'll do better next time." +Seulement une ? On fera mieux la prochaine fois. + +#ai-next // "There's always next time, Commander. The universe is full of boxes." +Il y aura toujours une prochaine fois, Commandant. L'univers est plein de boites. diff --git a/content/adventures/space/intro.lor b/content/adventures/space/intro.lor new file mode 100644 index 0000000..bbdd5ea --- /dev/null +++ b/content/adventures/space/intro.lor @@ -0,0 +1,191 @@ +// Space Adventure - Open The Box +// Theme: Space exploration with boxes floating in the void + +character captain + name: Captain Nova + +character ai + name: ARIA + role: Ship AI + +character alien + name: Zx'thorp + role: Alien Merchant + +state + fuel: 100 + discovered: 0 + hasSpaceBox: false + trustAlien: false + +beat Intro + The ship hums quietly as you drift through sector 7-G. #intro-hum + ai: Commander, scanners are detecting something unusual. #intro-scan + ai: A box. Floating in space. Defying several laws of physics. #intro-box + captain: A box? In space? #captain-box + + choice + Investigate the box #opt-investigate + -> InvestigateBox + Ignore it and continue #opt-ignore + -> IgnoreBox + Run a deep scan first #opt-scan if fuel > 20 + fuel -= 20 + -> DeepScan + +beat DeepScan + ai: Deep scan initiated. Fuel reserves reduced. #deepscan-init + ai: Analysis complete. The box appears to be... sentient? #deepscan-result + ai: It's humming a tune. I believe it's... the box theme. #deepscan-humming + captain: The box theme? #captain-theme + ai: Every box has a theme, Commander. Didn't you know? #ai-theme + + choice + Open the sentient box #opt-open-sentient + -> OpenSpaceBox + Try to communicate with it #opt-communicate + -> CommunicateBox + Back away slowly #opt-backaway + -> BackAway + +beat InvestigateBox + You approach the box carefully. It's about a meter wide, floating serenely. #investigate-approach + captain: It's... beautiful. #captain-beautiful + ai: Commander, I wouldn't use that word for a box. #ai-beautiful + captain: You don't tell me what words to use for boxes, ARIA. #captain-words + + choice + Open it immediately #opt-open-now + -> OpenSpaceBox + Scan it first #opt-scan-first if fuel > 10 + fuel -= 10 + -> ScanThenOpen + Poke it with a stick #opt-poke + -> PokeBox + +beat PokeBox + You extend the ship's robotic arm and gently poke the box. #poke-arm + The box rotates 90 degrees and reveals a label: "THIS SIDE UP" #poke-label + ai: Commander, I don't think boxes in zero gravity have an "up". #ai-gravity + captain: Maybe the box defines its own reality. #captain-reality + ai: That's... philosophically concerning. #ai-philosophy + -> OpenSpaceBox + +beat ScanThenOpen + ai: Scan reveals the box contains crystallized starlight and something called a "Nebula Shard". #scan-contents + ai: Also what appears to be a very small, very angry alien. #scan-alien + captain: Define "very small". #captain-small + ai: Approximately the size of a box. Commander, I think the alien IS a box. #ai-alienbox + -> OpenSpaceBox + +beat IgnoreBox + You set course away from the mysterious box. #ignore-course + ai: Commander, the box is... following us. #ai-following + captain: What? #captain-what + ai: It appears to have tiny thrusters. And what I can only describe as determination. #ai-thrusters + captain: A determined box. My favorite kind. #captain-determined + discovered += 1 + -> OpenSpaceBox + +beat CommunicateBox + captain: ARIA, open a channel to the box. #comm-channel + ai: Commander, it's a box. #ai-itsabox + captain: I said open a channel. #captain-channel + ai: Channel open, Commander. #ai-channelopen + captain: Hello, box. I am Captain Nova. I come in peace. #captain-peace + The box vibrates gently. It seems pleased. #comm-vibrate + ai: Commander, the box just sent us coordinates. And what appears to be... a friendship request? #ai-friendship + trustAlien = true + -> OpenSpaceBox + +beat BackAway + You engage reverse thrusters. The sentient box watches you leave. #back-thrusters + ai: Commander, the box looks... sad. #ai-sad + captain: Boxes don't have feelings, ARIA. #captain-feelings + ai: This one does. I'm reading elevated levels of box-sadness. #ai-boxsadness + captain: That's not a real metric. #captain-metric + ai: It is now. Box-sadness level: 7 out of 10. #ai-level + + choice + Go back to the box #opt-goback + -> CommunicateBox + Leave for good #opt-leave + -> LeaveForGood + +beat LeaveForGood + You leave the sentient box behind. #leave-behind + ai: I hope you're happy, Commander. #ai-happy + captain: I am. No weird space boxes for me today. #captain-happy + ai: Incoming transmission. It's... from the box. #ai-transmission + ai: It says "you'll be back. they always come back." #ai-message + captain: That's ominous. I love it. #captain-ominous + -> Ending + +beat OpenSpaceBox + hasSpaceBox = true + discovered += 1 + You carefully open the box. Light spills out from within. #open-light + + if trustAlien + Inside you find a Nebula Shard, pulsing with friendly energy. #open-friendly + alien: Ah, a fellow box appreciator! Take this as a gift from my collection. #alien-gift + captain: The alien WAS the box? #captain-wasbox + alien: We prefer "Box-Americans". Just kidding. I'm Zx'thorp. #alien-intro + -> AlienEncounter + else + Inside you find a Nebula Shard and what appears to be a map to more boxes. #open-normal + ai: Commander, the map shows three more floating boxes in nearby sectors. #ai-morebox + captain: Three more boxes. This is either Christmas or a trap. #captain-christmas + ai: In space, those are often the same thing. #ai-same + -> SpaceExploration + +beat AlienEncounter + alien: I've been collecting boxes across the galaxy for centuries. #alien-collecting + alien: Most species open them. Very few appreciate them. #alien-appreciate + captain: I appreciate boxes. Especially ones with good loot. #captain-loot + alien: "Loot." What a crude word for cosmic treasures. #alien-crude + + choice + Ask about the galaxy's rarest box #opt-rare-box + alien: The Singularity Box. It contains everything and nothing. Also a coupon. #alien-singularity + -> Ending + Trade items with Zx'thorp #opt-trade + alien: I have a Space Helmet for you. In exchange, I want your sense of wonder. #alien-trade + captain: Deal. Wait-- #captain-deal + -> Ending + Challenge the alien to a box-opening contest #opt-contest + alien: You dare? No one out-opens Zx'thorp! #alien-dare + alien: ...fine. Best of three. You open first. #alien-contest + -> Ending + +beat SpaceExploration + ai: Setting course for the nearest box. ETA: approximately one dramatic pause. #explore-course + + You arrive at a cluster of three boxes, orbiting each other like a tiny solar system. #explore-cluster + captain: A box system. Literally a system of boxes. #captain-system + ai: The large one appears to contain the other two. It's boxes all the way down. #ai-alltheway + + choice + Open the biggest box first #opt-big + You open the biggest box. Inside: two medium boxes and a note. #big-open + The note reads: "Congratulations! You've found the Box Nebula. Population: boxes." #big-note + -> Ending + Open all three at once #opt-all + captain: All three, ARIA. Maximum box velocity. #captain-velocity + ai: That's not a real measurement, but I admire your enthusiasm. #ai-enthusiasm + All three boxes burst open simultaneously. The contents mix together in zero gravity. #all-open + ai: Commander, the items are forming... a bigger box. #ai-biggerbox + captain: Of course they are. #captain-ofcourse + -> Ending + Leave them orbiting, they seem happy #opt-happy + captain: Let them orbit in peace. #captain-orbit + ai: A surprisingly philosophical choice, Commander. #ai-philosophical + ai: The boxes seem to orbit faster. I think they're happy. #ai-happy + -> Ending + +beat Ending + ai: Adventure complete. Updating ship's log. #ending-complete + ai: Boxes encountered: $discovered. Box-related existential crises: 1. #ending-log + captain: Only one? We'll do better next time. #captain-next + ai: There's always next time, Commander. The universe is full of boxes. #ai-next + -> . diff --git a/content/data/boxes.json b/content/data/boxes.json new file mode 100644 index 0000000..164bb0c --- /dev/null +++ b/content/data/boxes.json @@ -0,0 +1,558 @@ +[ + { + "id": "box_starter", + "nameKey": "box.starter", + "descriptionKey": "box.starter.desc", + "rarity": "Common", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": ["box_of_boxes"], + "rollCount": 0, + "entries": [] + } + }, + { + "id": "box_of_boxes", + "nameKey": "box.box_of_boxes", + "descriptionKey": "box.box_of_boxes.desc", + "rarity": "Common", + "isAutoOpen": true, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 1, + "entries": [ + {"itemDefinitionId": "box_not_great", "weight": 10}, + {"itemDefinitionId": "box_ok_tier", "weight": 5}, + {"itemDefinitionId": "box_cool", "weight": 1}, + {"itemDefinitionId": "box_epic", "weight": 1}, + {"itemDefinitionId": "box_legendhair", "weight": 1}, + {"itemDefinitionId": "box_legendary", "weight": 1}, + {"itemDefinitionId": "box_adventure", "weight": 1}, + {"itemDefinitionId": "box_style", "weight": 1}, + {"itemDefinitionId": "box_improvement", "weight": 1, "condition": {"type": "ResourceAbove", "targetId": "any", "value": 0}}, + {"itemDefinitionId": "box_supply", "weight": 1, "condition": {"type": "ResourceAbove", "targetId": "any", "value": 0}} + ] + } + }, + { + "id": "box_not_great", + "nameKey": "box.not_great", + "descriptionKey": "box.not_great.desc", + "rarity": "Common", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 1, + "entries": [ + {"itemDefinitionId": "material_wood_raw", "weight": 5}, + {"itemDefinitionId": "material_bronze_raw", "weight": 3}, + {"itemDefinitionId": "food_ration", "weight": 4}, + {"itemDefinitionId": "cosmetic_hair_short", "weight": 2}, + {"itemDefinitionId": "cosmetic_eyes_brown", "weight": 2}, + {"itemDefinitionId": "cosmetic_body_tshirt", "weight": 2}, + {"itemDefinitionId": "cosmetic_legs_short", "weight": 2}, + {"itemDefinitionId": "cosmetic_arms_regular", "weight": 2}, + {"itemDefinitionId": "tint_light", "weight": 1}, + {"itemDefinitionId": "tint_dark", "weight": 1} + ] + } + }, + { + "id": "box_ok_tier", + "nameKey": "box.ok_tier", + "descriptionKey": "box.ok_tier.desc", + "rarity": "Uncommon", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 2, + "entries": [ + {"itemDefinitionId": "material_iron_raw", "weight": 4}, + {"itemDefinitionId": "material_bronze_ingot", "weight": 3}, + {"itemDefinitionId": "health_potion_small", "weight": 4}, + {"itemDefinitionId": "mana_crystal_small", "weight": 3}, + {"itemDefinitionId": "gold_pouch", "weight": 3}, + {"itemDefinitionId": "cosmetic_hair_long", "weight": 2}, + {"itemDefinitionId": "cosmetic_eyes_blue", "weight": 2}, + {"itemDefinitionId": "cosmetic_eyes_green", "weight": 2}, + {"itemDefinitionId": "tint_cyan", "weight": 2}, + {"itemDefinitionId": "tint_orange", "weight": 2}, + {"itemDefinitionId": "box_meta", "weight": 1} + ] + } + }, + { + "id": "box_cool", + "nameKey": "box.cool", + "descriptionKey": "box.cool.desc", + "rarity": "Rare", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 3, + "entries": [ + {"itemDefinitionId": "material_steel_raw", "weight": 3}, + {"itemDefinitionId": "material_iron_ingot", "weight": 3}, + {"itemDefinitionId": "health_potion_medium", "weight": 3}, + {"itemDefinitionId": "mana_crystal_medium", "weight": 3}, + {"itemDefinitionId": "stamina_drink", "weight": 3}, + {"itemDefinitionId": "cosmetic_hair_ponytail", "weight": 2}, + {"itemDefinitionId": "cosmetic_eyes_sunglasses", "weight": 2}, + {"itemDefinitionId": "cosmetic_body_sexy", "weight": 2}, + {"itemDefinitionId": "tint_purple", "weight": 2}, + {"itemDefinitionId": "box_meta", "weight": 2}, + {"itemDefinitionId": "lore_1", "weight": 1}, + {"itemDefinitionId": "lore_2", "weight": 1} + ] + } + }, + { + "id": "box_epic", + "nameKey": "box.epic", + "descriptionKey": "box.epic.desc", + "rarity": "Epic", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": ["box_meta"], + "rollCount": 3, + "entries": [ + {"itemDefinitionId": "material_titanium_raw", "weight": 2}, + {"itemDefinitionId": "material_steel_ingot", "weight": 3}, + {"itemDefinitionId": "health_potion_large", "weight": 2}, + {"itemDefinitionId": "blood_vial", "weight": 2}, + {"itemDefinitionId": "energy_cell", "weight": 2}, + {"itemDefinitionId": "oxygen_tank", "weight": 2}, + {"itemDefinitionId": "cosmetic_hair_cyberpunk", "weight": 2}, + {"itemDefinitionId": "cosmetic_eyes_pilotglasses", "weight": 2}, + {"itemDefinitionId": "cosmetic_body_suit", "weight": 2}, + {"itemDefinitionId": "cosmetic_legs_pegleg", "weight": 2}, + {"itemDefinitionId": "tint_neon", "weight": 2}, + {"itemDefinitionId": "tint_silver", "weight": 2}, + {"itemDefinitionId": "lore_3", "weight": 1}, + {"itemDefinitionId": "lore_4", "weight": 1}, + {"itemDefinitionId": "lore_5", "weight": 1}, + {"itemDefinitionId": "box_adventure", "weight": 1} + ] + } + }, + { + "id": "box_legendhair", + "nameKey": "box.legendhair", + "descriptionKey": "box.legendhair.desc", + "rarity": "Legendary", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": ["cosmetic_hair_stardust"], + "rollCount": 1, + "entries": [ + {"itemDefinitionId": "cosmetic_hair_fire", "weight": 3}, + {"itemDefinitionId": "cosmetic_hair_cyberpunk", "weight": 3}, + {"itemDefinitionId": "tint_rainbow", "weight": 2}, + {"itemDefinitionId": "tint_gold", "weight": 2} + ] + } + }, + { + "id": "box_legendary", + "nameKey": "box.legendary", + "descriptionKey": "box.legendary.desc", + "rarity": "Legendary", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 4, + "entries": [ + {"itemDefinitionId": "material_diamond_raw", "weight": 3}, + {"itemDefinitionId": "material_titanium_ingot", "weight": 3}, + {"itemDefinitionId": "material_carbonfiber_raw", "weight": 3}, + {"itemDefinitionId": "cosmetic_body_armored", "weight": 2}, + {"itemDefinitionId": "cosmetic_body_robotic", "weight": 1}, + {"itemDefinitionId": "cosmetic_eyes_cybernetic", "weight": 2}, + {"itemDefinitionId": "cosmetic_legs_rocketboots", "weight": 2}, + {"itemDefinitionId": "cosmetic_legs_tentacles", "weight": 1}, + {"itemDefinitionId": "cosmetic_arms_mechanical", "weight": 2}, + {"itemDefinitionId": "cosmetic_arms_wings", "weight": 1}, + {"itemDefinitionId": "tint_void", "weight": 1}, + {"itemDefinitionId": "tint_rainbow", "weight": 1}, + {"itemDefinitionId": "lore_6", "weight": 1}, + {"itemDefinitionId": "lore_10", "weight": 1}, + {"itemDefinitionId": "box_meta", "weight": 2}, + {"itemDefinitionId": "box_black", "weight": 1} + ] + } + }, + { + "id": "box_meta", + "nameKey": "box.meta", + "descriptionKey": "box.meta.desc", + "rarity": "Rare", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 1, + "entries": [ + {"itemDefinitionId": "meta_colors", "weight": 5}, + {"itemDefinitionId": "meta_extended_colors", "weight": 3}, + {"itemDefinitionId": "meta_arrows", "weight": 4}, + {"itemDefinitionId": "meta_animation", "weight": 4}, + {"itemDefinitionId": "meta_inventory", "weight": 3}, + {"itemDefinitionId": "meta_resources", "weight": 3}, + {"itemDefinitionId": "meta_stats", "weight": 3}, + {"itemDefinitionId": "meta_portrait", "weight": 2}, + {"itemDefinitionId": "meta_chat", "weight": 2}, + {"itemDefinitionId": "meta_layout", "weight": 1}, + {"itemDefinitionId": "meta_shortcuts", "weight": 3}, + {"itemDefinitionId": "meta_crafting", "weight": 2}, + {"itemDefinitionId": "meta_resource_health", "weight": 2}, + {"itemDefinitionId": "meta_resource_mana", "weight": 2}, + {"itemDefinitionId": "meta_resource_food", "weight": 2}, + {"itemDefinitionId": "meta_resource_gold", "weight": 2}, + {"itemDefinitionId": "meta_resource_stamina", "weight": 1}, + {"itemDefinitionId": "meta_resource_blood", "weight": 1}, + {"itemDefinitionId": "meta_resource_oxygen", "weight": 1}, + {"itemDefinitionId": "meta_resource_energy", "weight": 1}, + {"itemDefinitionId": "meta_stat_strength", "weight": 1}, + {"itemDefinitionId": "meta_stat_intelligence", "weight": 1}, + {"itemDefinitionId": "meta_stat_luck", "weight": 1}, + {"itemDefinitionId": "meta_stat_charisma", "weight": 1}, + {"itemDefinitionId": "meta_font_consolas", "weight": 2}, + {"itemDefinitionId": "meta_font_firetruc", "weight": 1}, + {"itemDefinitionId": "meta_font_jetbrains", "weight": 2}, + {"itemDefinitionId": "box_meta", "weight": 3} + ] + } + }, + { + "id": "box_style", + "nameKey": "box.style", + "descriptionKey": "box.style.desc", + "rarity": "Uncommon", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 2, + "entries": [ + {"itemDefinitionId": "cosmetic_hair_short", "weight": 3}, + {"itemDefinitionId": "cosmetic_hair_long", "weight": 3}, + {"itemDefinitionId": "cosmetic_hair_ponytail", "weight": 2}, + {"itemDefinitionId": "cosmetic_hair_braided", "weight": 2}, + {"itemDefinitionId": "cosmetic_eyes_blue", "weight": 3}, + {"itemDefinitionId": "cosmetic_eyes_green", "weight": 3}, + {"itemDefinitionId": "cosmetic_eyes_redorange", "weight": 2}, + {"itemDefinitionId": "cosmetic_eyes_sunglasses", "weight": 2}, + {"itemDefinitionId": "cosmetic_body_tshirt", "weight": 3}, + {"itemDefinitionId": "cosmetic_body_sexy", "weight": 2}, + {"itemDefinitionId": "cosmetic_legs_short", "weight": 3}, + {"itemDefinitionId": "cosmetic_legs_panty", "weight": 2}, + {"itemDefinitionId": "cosmetic_arms_regular", "weight": 3}, + {"itemDefinitionId": "tint_cyan", "weight": 2}, + {"itemDefinitionId": "tint_orange", "weight": 2}, + {"itemDefinitionId": "tint_purple", "weight": 2}, + {"itemDefinitionId": "tint_warmpink", "weight": 2}, + {"itemDefinitionId": "cosmetic_gender_error", "weight": 1} + ] + } + }, + { + "id": "box_adventure", + "nameKey": "box.adventure", + "descriptionKey": "box.adventure.desc", + "rarity": "Rare", + "isAutoOpen": true, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 1, + "entries": [ + {"itemDefinitionId": "box_adventure_space", "weight": 1}, + {"itemDefinitionId": "box_adventure_medieval", "weight": 1}, + {"itemDefinitionId": "box_adventure_pirate", "weight": 1}, + {"itemDefinitionId": "box_adventure_contemporary", "weight": 1}, + {"itemDefinitionId": "box_adventure_sentimental", "weight": 1}, + {"itemDefinitionId": "box_adventure_prehistoric", "weight": 1}, + {"itemDefinitionId": "box_adventure_cosmic", "weight": 1}, + {"itemDefinitionId": "box_adventure_microscopic", "weight": 1}, + {"itemDefinitionId": "box_adventure_darkfantasy", "weight": 1} + ] + } + }, + { + "id": "box_adventure_space", + "nameKey": "box.adventure.space", + "descriptionKey": "box.adventure.space.desc", + "rarity": "Rare", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 2, + "entries": [ + {"itemDefinitionId": "space_badge", "weight": 3}, + {"itemDefinitionId": "space_phone", "weight": 3}, + {"itemDefinitionId": "space_coordinates", "weight": 3}, + {"itemDefinitionId": "space_key", "weight": 2}, + {"itemDefinitionId": "space_map", "weight": 2}, + {"itemDefinitionId": "oxygen_tank", "weight": 3}, + {"itemDefinitionId": "energy_cell", "weight": 2} + ] + } + }, + { + "id": "box_adventure_medieval", + "nameKey": "box.adventure.medieval", + "descriptionKey": "box.adventure.medieval.desc", + "rarity": "Rare", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 2, + "entries": [ + {"itemDefinitionId": "medieval_crest", "weight": 3}, + {"itemDefinitionId": "medieval_scroll", "weight": 3}, + {"itemDefinitionId": "medieval_seal", "weight": 2}, + {"itemDefinitionId": "medieval_key", "weight": 3}, + {"itemDefinitionId": "mysterious_key", "weight": 2}, + {"itemDefinitionId": "health_potion_medium", "weight": 3} + ] + } + }, + { + "id": "box_adventure_pirate", + "nameKey": "box.adventure.pirate", + "descriptionKey": "box.adventure.pirate.desc", + "rarity": "Rare", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": ["pirate_map"], + "rollCount": 2, + "entries": [ + {"itemDefinitionId": "pirate_compass", "weight": 3}, + {"itemDefinitionId": "pirate_feather", "weight": 4}, + {"itemDefinitionId": "pirate_rum", "weight": 3}, + {"itemDefinitionId": "pirate_key", "weight": 2}, + {"itemDefinitionId": "mysterious_key", "weight": 2}, + {"itemDefinitionId": "gold_pouch", "weight": 4} + ] + } + }, + { + "id": "box_adventure_contemporary", + "nameKey": "box.adventure.contemporary", + "descriptionKey": "box.adventure.contemporary.desc", + "rarity": "Rare", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 2, + "entries": [ + {"itemDefinitionId": "contemporary_phone", "weight": 3}, + {"itemDefinitionId": "contemporary_card", "weight": 3}, + {"itemDefinitionId": "contemporary_usb", "weight": 2}, + {"itemDefinitionId": "contemporary_key", "weight": 3}, + {"itemDefinitionId": "contemporary_badge", "weight": 3}, + {"itemDefinitionId": "mysterious_key", "weight": 2} + ] + } + }, + { + "id": "box_adventure_sentimental", + "nameKey": "box.adventure.sentimental", + "descriptionKey": "box.adventure.sentimental.desc", + "rarity": "Rare", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 2, + "entries": [ + {"itemDefinitionId": "sentimental_letter", "weight": 3}, + {"itemDefinitionId": "sentimental_flower", "weight": 4}, + {"itemDefinitionId": "sentimental_teddy", "weight": 3}, + {"itemDefinitionId": "sentimental_phone", "weight": 3}, + {"itemDefinitionId": "health_potion_small", "weight": 2} + ] + } + }, + { + "id": "box_adventure_prehistoric", + "nameKey": "box.adventure.prehistoric", + "descriptionKey": "box.adventure.prehistoric.desc", + "rarity": "Rare", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 2, + "entries": [ + {"itemDefinitionId": "prehistoric_tooth", "weight": 4}, + {"itemDefinitionId": "prehistoric_amber", "weight": 3}, + {"itemDefinitionId": "prehistoric_fossil", "weight": 2}, + {"itemDefinitionId": "material_wood_raw", "weight": 4}, + {"itemDefinitionId": "food_ration", "weight": 4} + ] + } + }, + { + "id": "box_adventure_cosmic", + "nameKey": "box.adventure.cosmic", + "descriptionKey": "box.adventure.cosmic.desc", + "rarity": "Epic", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 2, + "entries": [ + {"itemDefinitionId": "cosmic_shard", "weight": 4}, + {"itemDefinitionId": "cosmic_crystal", "weight": 3}, + {"itemDefinitionId": "cosmic_core", "weight": 1}, + {"itemDefinitionId": "energy_cell", "weight": 3}, + {"itemDefinitionId": "tint_void", "weight": 1} + ] + } + }, + { + "id": "box_adventure_microscopic", + "nameKey": "box.adventure.microscopic", + "descriptionKey": "box.adventure.microscopic.desc", + "rarity": "Rare", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 2, + "entries": [ + {"itemDefinitionId": "microscopic_bacteria", "weight": 4}, + {"itemDefinitionId": "microscopic_dna", "weight": 3}, + {"itemDefinitionId": "microscopic_prion", "weight": 2}, + {"itemDefinitionId": "mana_crystal_small", "weight": 3}, + {"itemDefinitionId": "health_potion_small", "weight": 3} + ] + } + }, + { + "id": "box_adventure_darkfantasy", + "nameKey": "box.adventure.darkfantasy", + "descriptionKey": "box.adventure.darkfantasy.desc", + "rarity": "Epic", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 2, + "entries": [ + {"itemDefinitionId": "darkfantasy_ring", "weight": 3}, + {"itemDefinitionId": "darkfantasy_grimoire", "weight": 2}, + {"itemDefinitionId": "darkfantasy_gem", "weight": 1}, + {"itemDefinitionId": "darkfantasy_key", "weight": 3}, + {"itemDefinitionId": "mysterious_key", "weight": 2}, + {"itemDefinitionId": "blood_vial", "weight": 3} + ] + } + }, + { + "id": "box_improvement", + "nameKey": "box.improvement", + "descriptionKey": "box.improvement.desc", + "rarity": "Uncommon", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 1, + "entries": [ + {"itemDefinitionId": "resource_max_health", "weight": 3}, + {"itemDefinitionId": "resource_max_mana", "weight": 3}, + {"itemDefinitionId": "resource_max_food", "weight": 3}, + {"itemDefinitionId": "resource_max_stamina", "weight": 3}, + {"itemDefinitionId": "resource_max_gold", "weight": 2}, + {"itemDefinitionId": "resource_max_blood", "weight": 1}, + {"itemDefinitionId": "resource_max_oxygen", "weight": 1}, + {"itemDefinitionId": "resource_max_energy", "weight": 1} + ] + } + }, + { + "id": "box_supply", + "nameKey": "box.supply", + "descriptionKey": "box.supply.desc", + "rarity": "Common", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 2, + "entries": [ + {"itemDefinitionId": "health_potion_small", "weight": 5}, + {"itemDefinitionId": "mana_crystal_small", "weight": 4}, + {"itemDefinitionId": "food_ration", "weight": 5}, + {"itemDefinitionId": "stamina_drink", "weight": 4}, + {"itemDefinitionId": "gold_pouch", "weight": 4}, + {"itemDefinitionId": "blood_vial", "weight": 1}, + {"itemDefinitionId": "oxygen_tank", "weight": 1}, + {"itemDefinitionId": "energy_cell", "weight": 1} + ] + } + }, + { + "id": "box_black", + "nameKey": "box.black", + "descriptionKey": "box.black.desc", + "rarity": "Mythic", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 3, + "entries": [ + {"itemDefinitionId": "mysterious_key", "weight": 3}, + {"itemDefinitionId": "lore_10", "weight": 2}, + {"itemDefinitionId": "cosmetic_gender_error", "weight": 1}, + {"itemDefinitionId": "tint_void", "weight": 2}, + {"itemDefinitionId": "material_diamond_raw", "weight": 2}, + {"itemDefinitionId": "material_diamond_gem", "weight": 1}, + {"itemDefinitionId": "cosmic_core", "weight": 1}, + {"itemDefinitionId": "darkfantasy_gem", "weight": 1}, + {"itemDefinitionId": "box_meta", "weight": 2}, + {"itemDefinitionId": "box_cookie", "weight": 2} + ] + } + }, + { + "id": "box_story", + "nameKey": "box.story", + "descriptionKey": "box.story.desc", + "rarity": "Rare", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": [], + "rollCount": 2, + "entries": [ + {"itemDefinitionId": "lore_1", "weight": 2}, + {"itemDefinitionId": "lore_2", "weight": 2}, + {"itemDefinitionId": "lore_3", "weight": 2}, + {"itemDefinitionId": "lore_4", "weight": 2}, + {"itemDefinitionId": "lore_5", "weight": 2}, + {"itemDefinitionId": "lore_6", "weight": 1}, + {"itemDefinitionId": "lore_7", "weight": 2}, + {"itemDefinitionId": "lore_8", "weight": 2}, + {"itemDefinitionId": "lore_9", "weight": 2}, + {"itemDefinitionId": "lore_10", "weight": 1} + ] + } + }, + { + "id": "box_music", + "nameKey": "box.music", + "descriptionKey": "box.music.desc", + "rarity": "Rare", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": ["music_melody"], + "rollCount": 0, + "entries": [] + } + }, + { + "id": "box_cookie", + "nameKey": "box.cookie", + "descriptionKey": "box.cookie.desc", + "rarity": "Uncommon", + "isAutoOpen": false, + "lootTable": { + "guaranteedRolls": ["cookie_fortune"], + "rollCount": 0, + "entries": [] + } + } +] diff --git a/content/data/interactions.json b/content/data/interactions.json new file mode 100644 index 0000000..12a3bbc --- /dev/null +++ b/content/data/interactions.json @@ -0,0 +1,52 @@ +[ + { + "id": "key_chest_auto", + "requiredItemTags": ["Key"], + "requiredItemIds": null, + "resultType": "OpenBox", + "resultData": null, + "isAutomatic": true, + "priority": 10, + "descriptionKey": "interaction.key_chest" + }, + { + "id": "badge_adventure_space", + "requiredItemTags": ["Badge", "Space"], + "requiredItemIds": null, + "resultType": "Unlock", + "resultData": "adventure:Space", + "isAutomatic": true, + "priority": 5, + "descriptionKey": "adventure.start" + }, + { + "id": "phone_character_encounter", + "requiredItemTags": ["PhoneNumber"], + "requiredItemIds": null, + "resultType": "Unlock", + "resultData": "character", + "isAutomatic": false, + "priority": 3, + "descriptionKey": "interaction.phone_call" + }, + { + "id": "coordinates_map_combine", + "requiredItemTags": ["Coordinates"], + "requiredItemIds": ["space_map"], + "resultType": "Combine", + "resultData": "adventure_unlock:Space", + "isAutomatic": true, + "priority": 8, + "descriptionKey": "interaction.map_coordinates" + }, + { + "id": "pirate_map_compass", + "requiredItemTags": [], + "requiredItemIds": ["pirate_map", "pirate_compass"], + "resultType": "Unlock", + "resultData": "adventure:Pirate", + "isAutomatic": true, + "priority": 8, + "descriptionKey": "interaction.treasure_located" + } +] diff --git a/content/data/items.json b/content/data/items.json new file mode 100644 index 0000000..f8a7dd1 --- /dev/null +++ b/content/data/items.json @@ -0,0 +1,153 @@ +[ + {"id": "meta_colors", "nameKey": "meta.colors", "category": "Meta", "rarity": "Rare", "tags": ["Meta"], "metaUnlock": "TextColors"}, + {"id": "meta_extended_colors", "nameKey": "meta.extended_colors", "category": "Meta", "rarity": "Rare", "tags": ["Meta"], "metaUnlock": "ExtendedColors"}, + {"id": "meta_arrows", "nameKey": "meta.arrows", "category": "Meta", "rarity": "Epic", "tags": ["Meta"], "metaUnlock": "ArrowKeySelection"}, + {"id": "meta_inventory", "nameKey": "meta.inventory", "category": "Meta", "rarity": "Rare", "tags": ["Meta"], "metaUnlock": "InventoryPanel"}, + {"id": "meta_resources", "nameKey": "meta.resources", "category": "Meta", "rarity": "Rare", "tags": ["Meta"], "metaUnlock": "ResourcePanel"}, + {"id": "meta_stats", "nameKey": "meta.stats", "category": "Meta", "rarity": "Rare", "tags": ["Meta"], "metaUnlock": "StatsPanel"}, + {"id": "meta_portrait", "nameKey": "meta.portrait", "category": "Meta", "rarity": "Epic", "tags": ["Meta"], "metaUnlock": "PortraitPanel"}, + {"id": "meta_chat", "nameKey": "meta.chat", "category": "Meta", "rarity": "Epic", "tags": ["Meta"], "metaUnlock": "ChatPanel"}, + {"id": "meta_layout", "nameKey": "meta.layout", "category": "Meta", "rarity": "Legendary", "tags": ["Meta"], "metaUnlock": "FullLayout"}, + {"id": "meta_shortcuts", "nameKey": "meta.shortcuts", "category": "Meta", "rarity": "Rare", "tags": ["Meta"], "metaUnlock": "KeyboardShortcuts"}, + {"id": "meta_animation", "nameKey": "meta.animation", "category": "Meta", "rarity": "Uncommon", "tags": ["Meta"], "metaUnlock": "BoxAnimation"}, + {"id": "meta_crafting", "nameKey": "meta.crafting", "category": "Meta", "rarity": "Epic", "tags": ["Meta"], "metaUnlock": "CraftingPanel"}, + {"id": "meta_resource_health", "nameKey": "resource.health", "category": "Meta", "rarity": "Uncommon", "tags": ["Meta", "ResourceVisibility"], "resourceType": "Health"}, + {"id": "meta_resource_mana", "nameKey": "resource.mana", "category": "Meta", "rarity": "Uncommon", "tags": ["Meta", "ResourceVisibility"], "resourceType": "Mana"}, + {"id": "meta_resource_food", "nameKey": "resource.food", "category": "Meta", "rarity": "Uncommon", "tags": ["Meta", "ResourceVisibility"], "resourceType": "Food"}, + {"id": "meta_resource_stamina", "nameKey": "resource.stamina", "category": "Meta", "rarity": "Uncommon", "tags": ["Meta", "ResourceVisibility"], "resourceType": "Stamina"}, + {"id": "meta_resource_blood", "nameKey": "resource.blood", "category": "Meta", "rarity": "Rare", "tags": ["Meta", "ResourceVisibility"], "resourceType": "Blood"}, + {"id": "meta_resource_gold", "nameKey": "resource.gold", "category": "Meta", "rarity": "Uncommon", "tags": ["Meta", "ResourceVisibility"], "resourceType": "Gold"}, + {"id": "meta_resource_oxygen", "nameKey": "resource.oxygen", "category": "Meta", "rarity": "Rare", "tags": ["Meta", "ResourceVisibility"], "resourceType": "Oxygen"}, + {"id": "meta_resource_energy", "nameKey": "resource.energy", "category": "Meta", "rarity": "Rare", "tags": ["Meta", "ResourceVisibility"], "resourceType": "Energy"}, + {"id": "meta_stat_strength", "nameKey": "stat.strength", "category": "Meta", "rarity": "Rare", "tags": ["Meta", "StatVisibility"], "statType": "Strength"}, + {"id": "meta_stat_intelligence", "nameKey": "stat.intelligence", "category": "Meta", "rarity": "Rare", "tags": ["Meta", "StatVisibility"], "statType": "Intelligence"}, + {"id": "meta_stat_luck", "nameKey": "stat.luck", "category": "Meta", "rarity": "Rare", "tags": ["Meta", "StatVisibility"], "statType": "Luck"}, + {"id": "meta_stat_charisma", "nameKey": "stat.charisma", "category": "Meta", "rarity": "Rare", "tags": ["Meta", "StatVisibility"], "statType": "Charisma"}, + {"id": "meta_stat_dexterity", "nameKey": "stat.dexterity", "category": "Meta", "rarity": "Rare", "tags": ["Meta", "StatVisibility"], "statType": "Dexterity"}, + {"id": "meta_stat_wisdom", "nameKey": "stat.wisdom", "category": "Meta", "rarity": "Rare", "tags": ["Meta", "StatVisibility"], "statType": "Wisdom"}, + {"id": "meta_font_consolas", "nameKey": "Consolas", "category": "Meta", "rarity": "Uncommon", "tags": ["Meta", "Font"], "fontStyle": "Consolas"}, + {"id": "meta_font_firetruc", "nameKey": "Firetruc", "category": "Meta", "rarity": "Rare", "tags": ["Meta", "Font"], "fontStyle": "Firetruc"}, + {"id": "meta_font_jetbrains", "nameKey": "JetBrains Mono", "category": "Meta", "rarity": "Rare", "tags": ["Meta", "Font"], "fontStyle": "Jetbrains"}, + + {"id": "cosmetic_hair_short", "nameKey": "cosmetic.hair.short", "category": "Cosmetic", "rarity": "Common", "tags": ["Cosmetic"], "cosmeticSlot": "Hair", "cosmeticValue": "Short"}, + {"id": "cosmetic_hair_long", "nameKey": "cosmetic.hair.long", "category": "Cosmetic", "rarity": "Common", "tags": ["Cosmetic"], "cosmeticSlot": "Hair", "cosmeticValue": "Long"}, + {"id": "cosmetic_hair_ponytail", "nameKey": "cosmetic.hair.ponytail", "category": "Cosmetic", "rarity": "Uncommon", "tags": ["Cosmetic"], "cosmeticSlot": "Hair", "cosmeticValue": "Ponytail"}, + {"id": "cosmetic_hair_braided", "nameKey": "cosmetic.hair.braided", "category": "Cosmetic", "rarity": "Uncommon", "tags": ["Cosmetic"], "cosmeticSlot": "Hair", "cosmeticValue": "Braided"}, + {"id": "cosmetic_hair_cyberpunk", "nameKey": "cosmetic.hair.cyberpunk", "category": "Cosmetic", "rarity": "Rare", "tags": ["Cosmetic"], "cosmeticSlot": "Hair", "cosmeticValue": "Cyberpunk"}, + {"id": "cosmetic_hair_fire", "nameKey": "cosmetic.hair.fire", "category": "Cosmetic", "rarity": "Epic", "tags": ["Cosmetic"], "cosmeticSlot": "Hair", "cosmeticValue": "Fire"}, + {"id": "cosmetic_hair_stardust", "nameKey": "cosmetic.hair.stardust", "category": "Cosmetic", "rarity": "Legendary", "tags": ["Cosmetic"], "cosmeticSlot": "Hair", "cosmeticValue": "StardustLegendary"}, + {"id": "cosmetic_eyes_blue", "nameKey": "cosmetic.eyes.blue", "category": "Cosmetic", "rarity": "Common", "tags": ["Cosmetic"], "cosmeticSlot": "Eyes", "cosmeticValue": "Blue"}, + {"id": "cosmetic_eyes_green", "nameKey": "cosmetic.eyes.green", "category": "Cosmetic", "rarity": "Common", "tags": ["Cosmetic"], "cosmeticSlot": "Eyes", "cosmeticValue": "Green"}, + {"id": "cosmetic_eyes_redorange", "nameKey": "cosmetic.eyes.redorange", "category": "Cosmetic", "rarity": "Uncommon", "tags": ["Cosmetic"], "cosmeticSlot": "Eyes", "cosmeticValue": "RedOrange"}, + {"id": "cosmetic_eyes_brown", "nameKey": "cosmetic.eyes.brown", "category": "Cosmetic", "rarity": "Common", "tags": ["Cosmetic"], "cosmeticSlot": "Eyes", "cosmeticValue": "Brown"}, + {"id": "cosmetic_eyes_sunglasses", "nameKey": "cosmetic.eyes.sunglasses", "category": "Cosmetic", "rarity": "Uncommon", "tags": ["Cosmetic"], "cosmeticSlot": "Eyes", "cosmeticValue": "Sunglasses"}, + {"id": "cosmetic_eyes_pilotglasses", "nameKey": "cosmetic.eyes.pilotglasses", "category": "Cosmetic", "rarity": "Rare", "tags": ["Cosmetic"], "cosmeticSlot": "Eyes", "cosmeticValue": "PilotGlasses"}, + {"id": "cosmetic_eyes_cybernetic", "nameKey": "cosmetic.eyes.cybernetic", "category": "Cosmetic", "rarity": "Epic", "tags": ["Cosmetic"], "cosmeticSlot": "Eyes", "cosmeticValue": "CyberneticEyes"}, + {"id": "cosmetic_eyes_magician", "nameKey": "cosmetic.eyes.magician", "category": "Cosmetic", "rarity": "Rare", "tags": ["Cosmetic"], "cosmeticSlot": "Eyes", "cosmeticValue": "MagicianGlasses"}, + {"id": "cosmetic_body_tshirt", "nameKey": "cosmetic.body.regulartshirt", "category": "Cosmetic", "rarity": "Common", "tags": ["Cosmetic"], "cosmeticSlot": "Body", "cosmeticValue": "RegularTShirt"}, + {"id": "cosmetic_body_sexy", "nameKey": "cosmetic.body.sexytshirt", "category": "Cosmetic", "rarity": "Uncommon", "tags": ["Cosmetic"], "cosmeticSlot": "Body", "cosmeticValue": "SexyTShirt"}, + {"id": "cosmetic_body_suit", "nameKey": "cosmetic.body.suit", "category": "Cosmetic", "rarity": "Rare", "tags": ["Cosmetic"], "cosmeticSlot": "Body", "cosmeticValue": "Suit"}, + {"id": "cosmetic_body_armored", "nameKey": "cosmetic.body.armored", "category": "Cosmetic", "rarity": "Epic", "tags": ["Cosmetic"], "cosmeticSlot": "Body", "cosmeticValue": "Armored"}, + {"id": "cosmetic_body_robotic", "nameKey": "cosmetic.body.robotic", "category": "Cosmetic", "rarity": "Legendary", "tags": ["Cosmetic"], "cosmeticSlot": "Body", "cosmeticValue": "Robotic"}, + {"id": "cosmetic_legs_short", "nameKey": "cosmetic.legs.short", "category": "Cosmetic", "rarity": "Common", "tags": ["Cosmetic"], "cosmeticSlot": "Legs", "cosmeticValue": "Short"}, + {"id": "cosmetic_legs_panty", "nameKey": "cosmetic.legs.panty", "category": "Cosmetic", "rarity": "Uncommon", "tags": ["Cosmetic"], "cosmeticSlot": "Legs", "cosmeticValue": "Panty"}, + {"id": "cosmetic_legs_rocketboots", "nameKey": "cosmetic.legs.rocketboots", "category": "Cosmetic", "rarity": "Epic", "tags": ["Cosmetic"], "cosmeticSlot": "Legs", "cosmeticValue": "RocketBoots"}, + {"id": "cosmetic_legs_pegleg", "nameKey": "cosmetic.legs.pegleg", "category": "Cosmetic", "rarity": "Rare", "tags": ["Cosmetic"], "cosmeticSlot": "Legs", "cosmeticValue": "PegLeg"}, + {"id": "cosmetic_legs_tentacles", "nameKey": "cosmetic.legs.tentacles", "category": "Cosmetic", "rarity": "Legendary", "tags": ["Cosmetic"], "cosmeticSlot": "Legs", "cosmeticValue": "Tentacles"}, + {"id": "cosmetic_arms_regular", "nameKey": "cosmetic.arms.regular", "category": "Cosmetic", "rarity": "Common", "tags": ["Cosmetic"], "cosmeticSlot": "Arms", "cosmeticValue": "Regular"}, + {"id": "cosmetic_arms_mechanical", "nameKey": "cosmetic.arms.mechanical", "category": "Cosmetic", "rarity": "Epic", "tags": ["Cosmetic"], "cosmeticSlot": "Arms", "cosmeticValue": "Mechanical"}, + {"id": "cosmetic_arms_wings", "nameKey": "cosmetic.arms.wings", "category": "Cosmetic", "rarity": "Legendary", "tags": ["Cosmetic"], "cosmeticSlot": "Arms", "cosmeticValue": "Wings"}, + {"id": "cosmetic_arms_extrapair", "nameKey": "cosmetic.arms.extrapair", "category": "Cosmetic", "rarity": "Epic", "tags": ["Cosmetic"], "cosmeticSlot": "Arms", "cosmeticValue": "ExtraPair"}, + {"id": "cosmetic_gender_error", "nameKey": "cosmetic.gender_error", "category": "Cosmetic", "rarity": "Mythic", "tags": ["Cosmetic", "Error", "Easter Egg"]}, + + {"id": "tint_cyan", "nameKey": "tint.cyan", "category": "Cosmetic", "rarity": "Common", "tags": ["Tint"], "tintColor": "Cyan"}, + {"id": "tint_orange", "nameKey": "tint.orange", "category": "Cosmetic", "rarity": "Common", "tags": ["Tint"], "tintColor": "Orange"}, + {"id": "tint_purple", "nameKey": "tint.purple", "category": "Cosmetic", "rarity": "Uncommon", "tags": ["Tint"], "tintColor": "Purple"}, + {"id": "tint_warmpink", "nameKey": "tint.warmpink", "category": "Cosmetic", "rarity": "Uncommon", "tags": ["Tint"], "tintColor": "WarmPink"}, + {"id": "tint_light", "nameKey": "tint.light", "category": "Cosmetic", "rarity": "Common", "tags": ["Tint"], "tintColor": "Light"}, + {"id": "tint_dark", "nameKey": "tint.dark", "category": "Cosmetic", "rarity": "Common", "tags": ["Tint"], "tintColor": "Dark"}, + {"id": "tint_rainbow", "nameKey": "tint.rainbow", "category": "Cosmetic", "rarity": "Legendary", "tags": ["Tint"], "tintColor": "Rainbow"}, + {"id": "tint_neon", "nameKey": "tint.neon", "category": "Cosmetic", "rarity": "Rare", "tags": ["Tint"], "tintColor": "Neon"}, + {"id": "tint_silver", "nameKey": "tint.silver", "category": "Cosmetic", "rarity": "Rare", "tags": ["Tint"], "tintColor": "Silver"}, + {"id": "tint_gold", "nameKey": "tint.gold", "category": "Cosmetic", "rarity": "Epic", "tags": ["Tint"], "tintColor": "Gold"}, + {"id": "tint_void", "nameKey": "tint.void", "category": "Cosmetic", "rarity": "Legendary", "tags": ["Tint"], "tintColor": "Void"}, + + {"id": "health_potion_small", "nameKey": "item.health_potion_small", "category": "Consumable", "rarity": "Common", "tags": ["Consumable"], "resourceType": "Health", "resourceAmount": 10}, + {"id": "health_potion_medium", "nameKey": "item.health_potion_medium", "category": "Consumable", "rarity": "Uncommon", "tags": ["Consumable"], "resourceType": "Health", "resourceAmount": 25}, + {"id": "health_potion_large", "nameKey": "item.health_potion_large", "category": "Consumable", "rarity": "Rare", "tags": ["Consumable"], "resourceType": "Health", "resourceAmount": 50}, + {"id": "mana_crystal_small", "nameKey": "item.mana_crystal_small", "category": "Consumable", "rarity": "Common", "tags": ["Consumable"], "resourceType": "Mana", "resourceAmount": 10}, + {"id": "mana_crystal_medium", "nameKey": "item.mana_crystal_medium", "category": "Consumable", "rarity": "Uncommon", "tags": ["Consumable"], "resourceType": "Mana", "resourceAmount": 25}, + {"id": "food_ration", "nameKey": "item.food_ration", "category": "Consumable", "rarity": "Common", "tags": ["Consumable"], "resourceType": "Food", "resourceAmount": 15}, + {"id": "stamina_drink", "nameKey": "item.stamina_drink", "category": "Consumable", "rarity": "Common", "tags": ["Consumable"], "resourceType": "Stamina", "resourceAmount": 20}, + {"id": "blood_vial", "nameKey": "item.blood_vial", "category": "Consumable", "rarity": "Rare", "tags": ["Consumable"], "resourceType": "Blood", "resourceAmount": 5}, + {"id": "gold_pouch", "nameKey": "item.gold_pouch", "category": "Consumable", "rarity": "Common", "tags": ["Consumable"], "resourceType": "Gold", "resourceAmount": 50}, + {"id": "oxygen_tank", "nameKey": "item.oxygen_tank", "category": "Consumable", "rarity": "Uncommon", "tags": ["Consumable"], "resourceType": "Oxygen", "resourceAmount": 30}, + {"id": "energy_cell", "nameKey": "item.energy_cell", "category": "Consumable", "rarity": "Uncommon", "tags": ["Consumable"], "resourceType": "Energy", "resourceAmount": 20}, + + {"id": "space_badge", "nameKey": "item.space.badge", "category": "AdventureToken", "rarity": "Rare", "tags": ["Adventure", "Space"], "adventureTheme": "Space"}, + {"id": "space_phone", "nameKey": "item.space.phone", "category": "AdventureToken", "rarity": "Rare", "tags": ["Adventure", "Space", "PhoneNumber"], "adventureTheme": "Space"}, + {"id": "space_key", "nameKey": "item.space.key", "category": "Key", "rarity": "Rare", "tags": ["Adventure", "Space", "Key"], "adventureTheme": "Space"}, + {"id": "space_map", "nameKey": "item.space.map", "category": "Map", "rarity": "Epic", "tags": ["Adventure", "Space"], "adventureTheme": "Space"}, + {"id": "space_coordinates", "nameKey": "item.space.coordinates", "category": "AdventureToken", "rarity": "Epic", "tags": ["Adventure", "Space", "Coordinates"], "adventureTheme": "Space"}, + {"id": "medieval_crest", "nameKey": "item.medieval.crest", "category": "AdventureToken", "rarity": "Rare", "tags": ["Adventure", "Medieval"], "adventureTheme": "Medieval"}, + {"id": "medieval_sword", "nameKey": "item.medieval.sword", "category": "AdventureToken", "rarity": "Epic", "tags": ["Adventure", "Medieval"], "adventureTheme": "Medieval"}, + {"id": "medieval_scroll", "nameKey": "item.medieval.scroll", "category": "AdventureToken", "rarity": "Rare", "tags": ["Adventure", "Medieval"], "adventureTheme": "Medieval"}, + {"id": "medieval_seal", "nameKey": "item.medieval.seal", "category": "AdventureToken", "rarity": "Epic", "tags": ["Adventure", "Medieval"], "adventureTheme": "Medieval"}, + {"id": "medieval_key", "nameKey": "item.medieval.key", "category": "Key", "rarity": "Rare", "tags": ["Adventure", "Medieval", "Key"], "adventureTheme": "Medieval"}, + {"id": "pirate_map", "nameKey": "item.pirate.map", "category": "Map", "rarity": "Epic", "tags": ["Adventure", "Pirate"], "adventureTheme": "Pirate"}, + {"id": "pirate_compass", "nameKey": "item.pirate.compass", "category": "AdventureToken", "rarity": "Rare", "tags": ["Adventure", "Pirate"], "adventureTheme": "Pirate"}, + {"id": "pirate_feather", "nameKey": "item.pirate.feather", "category": "AdventureToken", "rarity": "Common", "tags": ["Adventure", "Pirate"], "adventureTheme": "Pirate"}, + {"id": "pirate_rum", "nameKey": "item.pirate.rum", "category": "Consumable", "rarity": "Uncommon", "tags": ["Adventure", "Pirate"], "adventureTheme": "Pirate", "resourceType": "Stamina", "resourceAmount": 30}, + {"id": "pirate_key", "nameKey": "item.pirate.key", "category": "Key", "rarity": "Rare", "tags": ["Adventure", "Pirate", "Key"], "adventureTheme": "Pirate"}, + {"id": "contemporary_phone", "nameKey": "item.contemporary.phone", "category": "AdventureToken", "rarity": "Common", "tags": ["Adventure", "Contemporary", "PhoneNumber"], "adventureTheme": "Contemporary"}, + {"id": "contemporary_card", "nameKey": "item.contemporary.card", "category": "AdventureToken", "rarity": "Uncommon", "tags": ["Adventure", "Contemporary"], "adventureTheme": "Contemporary"}, + {"id": "contemporary_usb", "nameKey": "item.contemporary.usb", "category": "AdventureToken", "rarity": "Rare", "tags": ["Adventure", "Contemporary"], "adventureTheme": "Contemporary"}, + {"id": "contemporary_key", "nameKey": "item.contemporary.key", "category": "Key", "rarity": "Uncommon", "tags": ["Adventure", "Contemporary", "Key"], "adventureTheme": "Contemporary"}, + {"id": "contemporary_badge", "nameKey": "item.contemporary.badge", "category": "AdventureToken", "rarity": "Rare", "tags": ["Adventure", "Contemporary", "Badge"], "adventureTheme": "Contemporary"}, + {"id": "sentimental_letter", "nameKey": "item.sentimental.letter", "category": "AdventureToken", "rarity": "Rare", "tags": ["Adventure", "Sentimental"], "adventureTheme": "Sentimental"}, + {"id": "sentimental_flower", "nameKey": "item.sentimental.flower", "category": "AdventureToken", "rarity": "Common", "tags": ["Adventure", "Sentimental"], "adventureTheme": "Sentimental"}, + {"id": "sentimental_teddy", "nameKey": "item.sentimental.teddy", "category": "AdventureToken", "rarity": "Uncommon", "tags": ["Adventure", "Sentimental"], "adventureTheme": "Sentimental"}, + {"id": "sentimental_phone", "nameKey": "item.sentimental.phone", "category": "AdventureToken", "rarity": "Rare", "tags": ["Adventure", "Sentimental", "PhoneNumber"], "adventureTheme": "Sentimental"}, + {"id": "prehistoric_tooth", "nameKey": "item.prehistoric.tooth", "category": "AdventureToken", "rarity": "Uncommon", "tags": ["Adventure", "Prehistoric"], "adventureTheme": "Prehistoric"}, + {"id": "prehistoric_amber", "nameKey": "item.prehistoric.amber", "category": "AdventureToken", "rarity": "Rare", "tags": ["Adventure", "Prehistoric"], "adventureTheme": "Prehistoric"}, + {"id": "prehistoric_fossil", "nameKey": "item.prehistoric.fossil", "category": "AdventureToken", "rarity": "Epic", "tags": ["Adventure", "Prehistoric"], "adventureTheme": "Prehistoric"}, + {"id": "cosmic_shard", "nameKey": "item.cosmic.shard", "category": "AdventureToken", "rarity": "Rare", "tags": ["Adventure", "Cosmic"], "adventureTheme": "Cosmic"}, + {"id": "cosmic_crystal", "nameKey": "item.cosmic.crystal", "category": "AdventureToken", "rarity": "Epic", "tags": ["Adventure", "Cosmic"], "adventureTheme": "Cosmic"}, + {"id": "cosmic_core", "nameKey": "item.cosmic.core", "category": "AdventureToken", "rarity": "Legendary", "tags": ["Adventure", "Cosmic"], "adventureTheme": "Cosmic"}, + {"id": "microscopic_bacteria", "nameKey": "item.microscopic.bacteria", "category": "AdventureToken", "rarity": "Uncommon", "tags": ["Adventure", "Microscopic"], "adventureTheme": "Microscopic"}, + {"id": "microscopic_dna", "nameKey": "item.microscopic.dna", "category": "AdventureToken", "rarity": "Rare", "tags": ["Adventure", "Microscopic"], "adventureTheme": "Microscopic"}, + {"id": "microscopic_prion", "nameKey": "item.microscopic.prion", "category": "AdventureToken", "rarity": "Epic", "tags": ["Adventure", "Microscopic"], "adventureTheme": "Microscopic"}, + {"id": "darkfantasy_ring", "nameKey": "item.darkfantasy.ring", "category": "AdventureToken", "rarity": "Rare", "tags": ["Adventure", "DarkFantasy"], "adventureTheme": "DarkFantasy"}, + {"id": "darkfantasy_grimoire", "nameKey": "item.darkfantasy.grimoire", "category": "AdventureToken", "rarity": "Epic", "tags": ["Adventure", "DarkFantasy"], "adventureTheme": "DarkFantasy"}, + {"id": "darkfantasy_gem", "nameKey": "item.darkfantasy.gem", "category": "AdventureToken", "rarity": "Legendary", "tags": ["Adventure", "DarkFantasy"], "adventureTheme": "DarkFantasy"}, + {"id": "darkfantasy_key", "nameKey": "item.darkfantasy.key", "category": "Key", "rarity": "Rare", "tags": ["Adventure", "DarkFantasy", "Key"], "adventureTheme": "DarkFantasy"}, + + {"id": "mysterious_key", "nameKey": "item.mysterious_key", "descriptionKey": "item.mysterious_key.desc", "category": "Key", "rarity": "Rare", "tags": ["Key"]}, + + {"id": "lore_1", "nameKey": "lore.fragment_1", "category": "LoreFragment", "rarity": "Uncommon", "tags": ["Lore"]}, + {"id": "lore_2", "nameKey": "lore.fragment_2", "category": "LoreFragment", "rarity": "Uncommon", "tags": ["Lore"]}, + {"id": "lore_3", "nameKey": "lore.fragment_3", "category": "LoreFragment", "rarity": "Rare", "tags": ["Lore"]}, + {"id": "lore_4", "nameKey": "lore.fragment_4", "category": "LoreFragment", "rarity": "Rare", "tags": ["Lore"]}, + {"id": "lore_5", "nameKey": "lore.fragment_5", "category": "LoreFragment", "rarity": "Rare", "tags": ["Lore"]}, + {"id": "lore_6", "nameKey": "lore.fragment_6", "category": "LoreFragment", "rarity": "Epic", "tags": ["Lore"]}, + {"id": "lore_7", "nameKey": "lore.fragment_7", "category": "LoreFragment", "rarity": "Rare", "tags": ["Lore"]}, + {"id": "lore_8", "nameKey": "lore.fragment_8", "category": "LoreFragment", "rarity": "Rare", "tags": ["Lore"]}, + {"id": "lore_9", "nameKey": "lore.fragment_9", "category": "LoreFragment", "rarity": "Rare", "tags": ["Lore"]}, + {"id": "lore_10", "nameKey": "lore.fragment_10", "category": "LoreFragment", "rarity": "Epic", "tags": ["Lore"]}, + + {"id": "material_wood_raw", "nameKey": "material.wood", "category": "Material", "rarity": "Common", "tags": ["Material"], "materialType": "Wood", "materialForm": "Raw"}, + {"id": "material_wood_refined", "nameKey": "material.wood", "category": "Material", "rarity": "Common", "tags": ["Material"], "materialType": "Wood", "materialForm": "Refined"}, + {"id": "material_wood_nail", "nameKey": "material.wood", "category": "Material", "rarity": "Common", "tags": ["Material"], "materialType": "Wood", "materialForm": "Nail"}, + {"id": "material_bronze_raw", "nameKey": "material.bronze", "category": "Material", "rarity": "Common", "tags": ["Material"], "materialType": "Bronze", "materialForm": "Raw"}, + {"id": "material_bronze_ingot", "nameKey": "material.bronze", "category": "Material", "rarity": "Uncommon", "tags": ["Material"], "materialType": "Bronze", "materialForm": "Ingot"}, + {"id": "material_iron_raw", "nameKey": "material.iron", "category": "Material", "rarity": "Common", "tags": ["Material"], "materialType": "Iron", "materialForm": "Raw"}, + {"id": "material_iron_ingot", "nameKey": "material.iron", "category": "Material", "rarity": "Uncommon", "tags": ["Material"], "materialType": "Iron", "materialForm": "Ingot"}, + {"id": "material_steel_raw", "nameKey": "material.steel", "category": "Material", "rarity": "Uncommon", "tags": ["Material"], "materialType": "Steel", "materialForm": "Raw"}, + {"id": "material_steel_ingot", "nameKey": "material.steel", "category": "Material", "rarity": "Rare", "tags": ["Material"], "materialType": "Steel", "materialForm": "Ingot"}, + {"id": "material_titanium_raw", "nameKey": "material.titanium", "category": "Material", "rarity": "Rare", "tags": ["Material"], "materialType": "Titanium", "materialForm": "Raw"}, + {"id": "material_titanium_ingot", "nameKey": "material.titanium", "category": "Material", "rarity": "Epic", "tags": ["Material"], "materialType": "Titanium", "materialForm": "Ingot"}, + {"id": "material_diamond_raw", "nameKey": "material.diamond", "category": "Material", "rarity": "Epic", "tags": ["Material"], "materialType": "Diamond", "materialForm": "Raw"}, + {"id": "material_diamond_gem", "nameKey": "material.diamond", "category": "Material", "rarity": "Legendary", "tags": ["Material"], "materialType": "Diamond", "materialForm": "Gem"}, + {"id": "material_carbonfiber_raw", "nameKey": "material.carbonfiber", "category": "Material", "rarity": "Rare", "tags": ["Material"], "materialType": "CarbonFiber", "materialForm": "Raw"}, + {"id": "material_carbonfiber_sheet", "nameKey": "material.carbonfiber", "category": "Material", "rarity": "Epic", "tags": ["Material"], "materialType": "CarbonFiber", "materialForm": "Sheet"} +] diff --git a/content/strings/en.json b/content/strings/en.json new file mode 100644 index 0000000..a2c74d4 --- /dev/null +++ b/content/strings/en.json @@ -0,0 +1,339 @@ +{ + "game.title": "OPEN THE BOX", + "game.subtitle": "What's inside? Only one way to find out.", + "game.version": "v0.1.0", + + "menu.new_game": "New Game", + "menu.load_game": "Load Game", + "menu.language": "Language", + "menu.quit": "Quit", + "menu.back": "Back", + "menu.continue": "Continue", + "menu.save": "Save Game", + "menu.settings": "Settings", + + "action.open_box": "Open a box", + "action.inventory": "View inventory", + "action.craft": "Craft", + "action.adventure": "Go on an adventure", + "action.appearance": "Change appearance", + "action.save": "Save", + "action.quit": "Return to menu", + + "prompt.name": "What is your name, brave box-opener?", + "prompt.choose_action": "What would you like to do?", + "prompt.choose_box": "Which box do you want to open?", + "prompt.choose_interaction": "Multiple interactions possible! Choose one:", + "prompt.press_key": "Press any key to continue...", + + "box.opening": "Opening {0}...", + "box.found": "You found: {0}!", + "box.found_box": "Inside was... another box! {0}!", + "box.empty": "The box is empty! How philosophical.", + "box.no_boxes": "You have no boxes. How did you manage that?", + "box.auto_open": "{0} opens automatically!", + + "box.starter": "Starter Box", + "box.starter.desc": "Your first box. The beginning of everything. Or nothing. Probably something though.", + "box.box_of_boxes": "Box of Boxes", + "box.box_of_boxes.desc": "A box that contains... boxes. It's boxes all the way down.", + "box.not_great": "Meh Box", + "box.not_great.desc": "It's not great. It's not terrible. It just... is.", + "box.ok_tier": "Okay-ish Box", + "box.ok_tier.desc": "Mediocrity has never looked so boxy.", + "box.cool": "Cool Box", + "box.cool.desc": "Now we're getting somewhere. Somewhere cool.", + "box.epic": "Epic Box", + "box.epic.desc": "The orchestra swells. The crowd gasps. It's... a box.", + "box.legendhair": "Legend'hair Box", + "box.legendhair.desc": "The most legendary hair you'll ever unbox. Hair today, legend tomorrow.", + "box.legendary": "Legendary Box", + "box.legendary.desc": "Legends speak of this box. They speak quietly though, it's a box.", + "box.adventure": "Adventure Box", + "box.adventure.desc": "Contains the key to adventure! Literally, sometimes.", + "box.style": "Style Box", + "box.style.desc": "Fashion is temporary. Style from a box is eternal.", + "box.improvement": "Improvement Box", + "box.improvement.desc": "You can always improve. Especially with boxes.", + "box.supply": "Supply Box", + "box.supply.desc": "Supplies! The lifeblood of any box-opening enthusiast.", + "box.meta": "Meta Box", + "box.meta.desc": "This box improves... the way you see boxes. How meta.", + "box.black": "Black Box", + "box.black.desc": "Nobody knows what's inside. Not even the box.", + "box.story": "Story Box", + "box.story.desc": "Every box has a story. This one more than most.", + "box.music": "Music Box", + "box.music.desc": "♪ Do do do do ♪ Box music is best music.", + "box.cookie": "Cookie Box", + "box.cookie.desc": "Fortune favors the bold. Also those who open boxes.", + + "box.adventure.space": "Space Adventure Box", + "box.adventure.space.desc": "To infinity and beyond! (Box not included in infinity)", + "box.adventure.medieval": "Medieval Adventure Box", + "box.adventure.medieval.desc": "Hear ye, hear ye! A box of ye olde adventure!", + "box.adventure.pirate": "Pirate Adventure Box", + "box.adventure.pirate.desc": "Arr! X marks the box!", + "box.adventure.contemporary": "Contemporary Adventure Box", + "box.adventure.contemporary.desc": "A box for modern times. Comes with WiFi anxiety.", + "box.adventure.sentimental": "Sentimental Adventure Box", + "box.adventure.sentimental.desc": "This box makes you feel things. Mostly curiosity.", + "box.adventure.prehistoric": "Prehistoric Adventure Box", + "box.adventure.prehistoric.desc": "Ooga booga box. Very old. Much mystery.", + "box.adventure.cosmic": "Cosmic Adventure Box", + "box.adventure.cosmic.desc": "The universe is a box. This box is a universe.", + "box.adventure.microscopic": "Microscopic Adventure Box", + "box.adventure.microscopic.desc": "Size doesn't matter. Except when it does. Zoom in!", + "box.adventure.darkfantasy": "Dark Fantasy Adventure Box", + "box.adventure.darkfantasy.desc": "Darkness awaits. Also a box. A dark box.", + + "meta.unlocked": "NEW FEATURE UNLOCKED: {0}!", + "meta.colors": "Text Colors", + "meta.extended_colors": "Extended Color Palette", + "meta.arrows": "Arrow Key Navigation", + "meta.inventory": "Inventory Panel", + "meta.resources": "Resource Panel", + "meta.stats": "Stats Panel", + "meta.portrait": "Portrait Panel", + "meta.chat": "Chat Panel", + "meta.layout": "Full Layout Mode", + "meta.shortcuts": "Keyboard Shortcuts", + "meta.animation": "Box Opening Animation", + "meta.crafting": "Crafting Panel", + + "item.rarity.common": "Common", + "item.rarity.uncommon": "Uncommon", + "item.rarity.rare": "Rare", + "item.rarity.epic": "Epic", + "item.rarity.legendary": "Legendary", + "item.rarity.mythic": "Mythic", + + "resource.health": "Health", + "resource.mana": "Mana", + "resource.food": "Food", + "resource.stamina": "Stamina", + "resource.blood": "Blood", + "resource.gold": "Gold", + "resource.oxygen": "Oxygen", + "resource.energy": "Energy", + + "stat.strength": "Strength", + "stat.intelligence": "Intelligence", + "stat.luck": "Luck", + "stat.charisma": "Charisma", + "stat.dexterity": "Dexterity", + "stat.wisdom": "Wisdom", + + "cosmetic.hair.none": "Bald", + "cosmetic.hair.short": "Short Hair", + "cosmetic.hair.long": "Long Hair", + "cosmetic.hair.ponytail": "Ponytail", + "cosmetic.hair.braided": "Braided Hair", + "cosmetic.hair.cyberpunk": "Cyberpunk Neon Hair", + "cosmetic.hair.fire": "Hair on Fire", + "cosmetic.hair.stardust": "Stardust Legendary Hair", + "cosmetic.eyes.none": "No Eyes (mysterious!)", + "cosmetic.eyes.blue": "Blue Eyes", + "cosmetic.eyes.green": "Green Eyes", + "cosmetic.eyes.redorange": "Red-Orange Eyes", + "cosmetic.eyes.brown": "Brown Eyes", + "cosmetic.eyes.black": "Black Eyes", + "cosmetic.eyes.sunglasses": "Sunglasses", + "cosmetic.eyes.pilotglasses": "Pilot Glasses", + "cosmetic.eyes.aircraftglasses": "Aircraft Glasses", + "cosmetic.eyes.cybernetic": "Cybernetic Eyes", + "cosmetic.eyes.magician": "Magician Glasses", + "cosmetic.body.naked": "Shirtless", + "cosmetic.body.regulartshirt": "Regular T-Shirt", + "cosmetic.body.sexytshirt": "Sexy T-Shirt", + "cosmetic.body.suit": "Business Suit", + "cosmetic.body.armored": "Armored Plate", + "cosmetic.body.robotic": "Robotic Chassis", + "cosmetic.legs.none": "Floating (no legs!)", + "cosmetic.legs.naked": "Bare Legs", + "cosmetic.legs.slip": "Slip", + "cosmetic.legs.short": "Shorts", + "cosmetic.legs.panty": "Panty", + "cosmetic.legs.rocketboots": "Rocket Boots", + "cosmetic.legs.pegleg": "Peg Leg", + "cosmetic.legs.tentacles": "Tentacles", + "cosmetic.arms.none": "No Arms (T-Rex mode)", + "cosmetic.arms.short": "Short Arms", + "cosmetic.arms.regular": "Regular Arms", + "cosmetic.arms.long": "Long Stretchy Arms", + "cosmetic.arms.mechanical": "Mechanical Arms", + "cosmetic.arms.wings": "Wings", + "cosmetic.arms.extrapair": "Four Arms", + + "cosmetic.gender_error": "New Gender (ERROR: boxes don't have a gender. The box apologizes for the confusion.)", + + "tint.none": "Natural", + "tint.cyan": "Cyan", + "tint.orange": "Orange", + "tint.purple": "Purple", + "tint.warmpink": "Warm Pink", + "tint.light": "Light", + "tint.dark": "Dark", + "tint.rainbow": "Rainbow", + "tint.neon": "Neon", + "tint.silver": "Silver", + "tint.gold": "Gold", + "tint.void": "Void", + + "material.wood": "Wood", + "material.bronze": "Bronze", + "material.iron": "Iron", + "material.steel": "Steel", + "material.titanium": "Titanium", + "material.diamond": "Diamond", + "material.carbonfiber": "Carbon Fiber", + "material.form.raw": "Raw", + "material.form.refined": "Refined", + "material.form.nail": "Nail", + "material.form.plank": "Plank", + "material.form.ingot": "Ingot", + "material.form.sheet": "Sheet", + "material.form.thread": "Thread", + "material.form.dust": "Dust", + "material.form.gem": "Gem", + + "item.health_potion_small": "Small Health Potion", + "item.health_potion_medium": "Medium Health Potion", + "item.health_potion_large": "Large Health Potion", + "item.mana_crystal_small": "Small Mana Crystal", + "item.mana_crystal_medium": "Medium Mana Crystal", + "item.food_ration": "Food Ration", + "item.stamina_drink": "Stamina Drink", + "item.blood_vial": "Blood Vial", + "item.gold_pouch": "Gold Pouch", + "item.oxygen_tank": "Oxygen Tank", + "item.energy_cell": "Energy Cell", + + "item.space.badge": "Astronaut Badge", + "item.space.phone": "Alien Phone Number", + "item.space.key": "Airlock Access Key", + "item.space.map": "Star Map", + "item.space.coordinates": "Mysterious Coordinates", + "item.space.helmet": "Space Helmet", + "item.medieval.crest": "Knight's Crest", + "item.medieval.sword": "Excalibur Replica", + "item.medieval.scroll": "Ancient Scroll", + "item.medieval.seal": "Royal Seal", + "item.medieval.key": "Dungeon Key", + "item.pirate.map": "Treasure Map", + "item.pirate.compass": "Enchanted Compass", + "item.pirate.feather": "Parrot Feather", + "item.pirate.rum": "Bottle of Rum", + "item.pirate.flag": "Jolly Roger", + "item.pirate.key": "Chest Key", + "item.contemporary.phone": "Smartphone", + "item.contemporary.card": "Credit Card", + "item.contemporary.ticket": "Metro Ticket", + "item.contemporary.usb": "Suspicious USB Drive", + "item.contemporary.key": "Apartment Key", + "item.contemporary.badge": "Company Badge", + "item.sentimental.letter": "Love Letter", + "item.sentimental.flower": "Dried Flower", + "item.sentimental.album": "Photo Album", + "item.sentimental.melody": "Music Box Melody", + "item.sentimental.teddy": "Old Teddy Bear", + "item.sentimental.phone": "Ex's Phone Number", + "item.prehistoric.tooth": "Dinosaur Tooth", + "item.prehistoric.painting": "Cave Painting Fragment", + "item.prehistoric.amber": "Amber Stone", + "item.prehistoric.club": "Bone Club", + "item.prehistoric.fossil": "Trilobite Fossil", + "item.cosmic.shard": "Nebula Shard", + "item.cosmic.fragment": "Black Hole Fragment", + "item.cosmic.crystal": "Quasar Crystal", + "item.cosmic.dust": "Cosmic Dust", + "item.cosmic.core": "Star Core", + "item.microscopic.bacteria": "Sentient Bacteria Sample", + "item.microscopic.dna": "Glowing DNA Strand", + "item.microscopic.membrane": "Reinforced Cell Membrane", + "item.microscopic.mitochondria": "Hyperactive Mitochondria", + "item.microscopic.prion": "Friendly Prion (probably)", + "item.darkfantasy.ring": "Cursed Ring", + "item.darkfantasy.rune": "Blood Rune", + "item.darkfantasy.cloak": "Shadow Cloak", + "item.darkfantasy.grimoire": "Necromancer's Grimoire", + "item.darkfantasy.gem": "Soul Gem", + "item.darkfantasy.key": "Bone Key", + + "item.resource_max_up": "{0} Max +1", + "item.resource_up": "{0} +1", + "item.stat_boost": "{0} +1", + + "item.mysterious_key": "Mysterious Key", + "item.mysterious_key.desc": "A key to... something. The box knows, but the box isn't talking.", + + "lore.fragment_1": "In the beginning, there was a box. The box contained another box. And so it was, and so it shall be.", + "lore.fragment_2": "The Ancient Order of Box-Openers has a single commandment: Open thy boxes.", + "lore.fragment_3": "Some say the universe itself is a box, waiting to be opened by someone with enough curiosity.", + "lore.fragment_4": "The first box was opened by Farah, who found inside it the concept of 'inside'.", + "lore.fragment_5": "Malkith once opened a box that contained the sound of one hand clapping. Nobody knows what that means.", + "lore.fragment_6": "Legend says there is a box that contains all other boxes. Opening it would cause a paradox. Or a refund.", + "lore.fragment_7": "Duncan tried to close a box once. The box union went on strike for three weeks.", + "lore.fragment_8": "Pierrick built a box-opening machine. It opened itself. Then it opened the machine. Then it opened the concept of opening.", + "lore.fragment_9": "Samuel wrote the original box-opening manual. Chapter 1: Open the box. Chapter 2: See Chapter 1.", + "lore.fragment_10": "The Black Box contains a cat. Or doesn't. Until you open it, it both does and doesn't. The cat is also a box.", + + "cookie.1": "A box within a box is still a box.", + "cookie.2": "ERROR: This cookie contains no fortune. Please try again.", + "cookie.3": "You will open many boxes. This prediction has a 100% accuracy rate.", + "cookie.4": "The real treasure was the boxes we opened along the way.", + "cookie.5": "WARNING: Side effects of box opening include joy, confusion, and tentacle arms.", + "cookie.6": "Tomorrow you will find a box. Then another. Then another. Send help.", + "cookie.7": "Your lucky number is the number of boxes you've opened. So... a lot.", + "cookie.8": "Confucius say: person who open box find box. Person who not open box also find box. Box is inevitable.", + "cookie.9": "A journey of a thousand boxes begins with a single open.", + "cookie.10": "If you're reading this, you've spent too long opening boxes. Just kidding, there's no such thing.", + "cookie.11": "The box giveth, and the box giveth more boxes.", + "cookie.12": "In Soviet Russia, box opens YOU.", + "cookie.13": "Help I'm trapped in a fortune cookie factory inside a box.", + "cookie.14": "This fortune intentionally left blank. Just kidding. Or am I?", + "cookie.15": "You are the chosen one. The one who opens boxes. Truly a noble calling.", + "cookie.16": "Plot twist: the box was the friends you made along the way.", + "cookie.17": "Schrodinger called. He wants his box concept back.", + "cookie.18": "If you open a box and no one is around to hear it, does it make a loot?", + "cookie.19": "Today is a good day to open boxes. Tomorrow too. Every day, really.", + "cookie.20": "Your spirit animal is a box. Your power move is opening.", + + "character.farah": "Farah", + "character.malkith": "Malkith", + "character.linu": "Linu", + "character.chenda": "Chenda", + "character.duncan": "Duncan", + "character.sandrea": "Sandrea", + "character.samuel": "Samuel", + "character.pierrick": "Pierrick", + "character.nova": "Captain Nova", + "character.aria": "ARIA", + "character.blackbeard": "Blackbeard the Unboxable", + "character.mordecai": "Mordecai the Grim", + "character.zephyr": "Zephyr", + "character.quantum": "Dr. Quantum", + + "adventure.start": "Begin {0} Adventure", + "adventure.resume": "Resume {0} Adventure", + "adventure.completed": "Adventure Complete! You are now a certified box adventurer.", + + "interaction.key_chest": "The key fits! The chest opens automatically!", + "interaction.key_no_match": "This key seems to fit something... but you don't have it yet. Perhaps a future box will provide.", + "interaction.craft_available": "New recipe available at {0}!", + + "save.saving": "Saving...", + "save.saved": "Game saved to slot '{0}'.", + "save.loading": "Loading...", + "save.loaded": "Game loaded from slot '{0}'.", + "save.no_saves": "No save files found.", + "save.choose_slot": "Choose a save slot:", + + "error.invalid_input": "Invalid input. Try again, brave box-opener.", + "error.no_boxes": "You have no boxes to open. How did you manage that? Open more boxes to get boxes.", + "error.not_enough_resources": "Not enough {0}. You need {1} more.", + + "misc.boxes_opened": "Total boxes opened: {0}", + "misc.play_time": "Play time: {0}", + "misc.welcome_back": "Welcome back, {0}! Your boxes missed you." +} diff --git a/content/strings/fr.json b/content/strings/fr.json new file mode 100644 index 0000000..c7e3bc4 --- /dev/null +++ b/content/strings/fr.json @@ -0,0 +1,339 @@ +{ + "game.title": "OUVRE LA BOITE", + "game.subtitle": "Qu'est-ce qu'il y a dedans ? Un seul moyen de le savoir.", + "game.version": "v0.1.0", + + "menu.new_game": "Nouvelle Partie", + "menu.load_game": "Charger une Partie", + "menu.language": "Langue", + "menu.quit": "Quitter", + "menu.back": "Retour", + "menu.continue": "Continuer", + "menu.save": "Sauvegarder", + "menu.settings": "Parametres", + + "action.open_box": "Ouvrir une boite", + "action.inventory": "Voir l'inventaire", + "action.craft": "Fabriquer", + "action.adventure": "Partir a l'aventure", + "action.appearance": "Changer d'apparence", + "action.save": "Sauvegarder", + "action.quit": "Retourner au menu", + + "prompt.name": "Quel est ton nom, brave ouvreur de boites ?", + "prompt.choose_action": "Que veux-tu faire ?", + "prompt.choose_box": "Quelle boite veux-tu ouvrir ?", + "prompt.choose_interaction": "Plusieurs interactions possibles ! Choisis-en une :", + "prompt.press_key": "Appuie sur une touche pour continuer...", + + "box.opening": "Ouverture de {0}...", + "box.found": "Tu as trouve : {0} !", + "box.found_box": "A l'interieur il y avait... une autre boite ! {0} !", + "box.empty": "La boite est vide ! Philosophique.", + "box.no_boxes": "Tu n'as aucune boite. Comment t'as fait ?", + "box.auto_open": "{0} s'ouvre automatiquement !", + + "box.starter": "Boite de depart", + "box.starter.desc": "Ta premiere boite. Le debut de tout. Ou de rien. Probablement de quelque chose quand meme.", + "box.box_of_boxes": "Boite a boite", + "box.box_of_boxes.desc": "Une boite qui contient... des boites. C'est des boites jusqu'en bas.", + "box.not_great": "Boite pas ouf", + "box.not_great.desc": "Elle est pas geniale. Elle est pas terrible. Elle... est.", + "box.ok_tier": "Boite ok tiers", + "box.ok_tier.desc": "La mediocrite n'a jamais ete aussi carree.", + "box.cool": "Boite coolos", + "box.cool.desc": "La on commence a causer. A causer cool.", + "box.epic": "Boite epique", + "box.epic.desc": "L'orchestre s'intensifie. La foule retient son souffle. C'est... une boite.", + "box.legendhair": "Boite legend'hair", + "box.legendhair.desc": "La coiffure la plus legendaire que tu deballeras jamais. Cheveu-jour-d'hui, legende demain.", + "box.legendary": "Boite legendaire", + "box.legendary.desc": "Les legendes parlent de cette boite. Doucement quand meme, c'est une boite.", + "box.adventure": "Boite aventure", + "box.adventure.desc": "Contient la cle de l'aventure ! Litteralement, parfois.", + "box.style": "Boite stylee", + "box.style.desc": "La mode est ephemere. Le style sorti d'une boite est eternel.", + "box.improvement": "Boite d'amelioration", + "box.improvement.desc": "On peut toujours s'ameliorer. Surtout avec des boites.", + "box.supply": "Boite de fourniture", + "box.supply.desc": "Des fournitures ! Le sang vital de tout passione d'ouverture de boites.", + "box.meta": "Boite Meta", + "box.meta.desc": "Cette boite ameliore... la facon dont tu vois les boites. Trop meta.", + "box.black": "Boite noire", + "box.black.desc": "Personne ne sait ce qu'il y a dedans. Meme pas la boite.", + "box.story": "Boite a histoire", + "box.story.desc": "Chaque boite a une histoire. Celle-ci plus que les autres.", + "box.music": "Boite a musique", + "box.music.desc": "Do do do do. La musique de boite c'est la meilleure musique.", + "box.cookie": "Boite a Cookies", + "box.cookie.desc": "La fortune sourit aux audacieux. Et a ceux qui ouvrent des boites.", + + "box.adventure.space": "Boite d'aventure spatiale", + "box.adventure.space.desc": "Vers l'infini et au-dela ! (Boite non incluse dans l'infini)", + "box.adventure.medieval": "Boite d'aventure medievale", + "box.adventure.medieval.desc": "Oyez, oyez ! Une boite d'aventure d'antan !", + "box.adventure.pirate": "Boite d'aventure pirate", + "box.adventure.pirate.desc": "Arr ! X marque la boite !", + "box.adventure.contemporary": "Boite d'aventure contemporaine", + "box.adventure.contemporary.desc": "Une boite pour les temps modernes. Livree avec l'anxiete du WiFi.", + "box.adventure.sentimental": "Boite d'aventure sentimentale", + "box.adventure.sentimental.desc": "Cette boite te fait ressentir des choses. Surtout de la curiosite.", + "box.adventure.prehistoric": "Boite d'aventure prehistorique", + "box.adventure.prehistoric.desc": "Ouga bouga boite. Tres vieille. Beaucoup mystere.", + "box.adventure.cosmic": "Boite d'aventure cosmique", + "box.adventure.cosmic.desc": "L'univers est une boite. Cette boite est un univers.", + "box.adventure.microscopic": "Boite d'aventure microscopique", + "box.adventure.microscopic.desc": "La taille ne compte pas. Sauf quand si. Zoom !", + "box.adventure.darkfantasy": "Boite d'aventure dark fantasy", + "box.adventure.darkfantasy.desc": "Les tenebres t'attendent. Et aussi une boite. Une boite sombre.", + + "meta.unlocked": "NOUVELLE FONCTIONNALITE : {0} !", + "meta.colors": "Couleurs de texte", + "meta.extended_colors": "Palette de couleurs etendue", + "meta.arrows": "Navigation avec les fleches", + "meta.inventory": "Panneau d'inventaire", + "meta.resources": "Panneau de ressources", + "meta.stats": "Panneau de statistiques", + "meta.portrait": "Panneau portrait", + "meta.chat": "Panneau de discussion", + "meta.layout": "Mise en page complete", + "meta.shortcuts": "Raccourcis clavier", + "meta.animation": "Animation d'ouverture de boite", + "meta.crafting": "Panneau de fabrication", + + "item.rarity.common": "Commun", + "item.rarity.uncommon": "Peu commun", + "item.rarity.rare": "Rare", + "item.rarity.epic": "Epique", + "item.rarity.legendary": "Legendaire", + "item.rarity.mythic": "Mythique", + + "resource.health": "Sante", + "resource.mana": "Mana", + "resource.food": "Nourriture", + "resource.stamina": "Endurance", + "resource.blood": "Sang", + "resource.gold": "Or", + "resource.oxygen": "Oxygene", + "resource.energy": "Energie", + + "stat.strength": "Force", + "stat.intelligence": "Intelligence", + "stat.luck": "Chance", + "stat.charisma": "Charisme", + "stat.dexterity": "Dexterite", + "stat.wisdom": "Sagesse", + + "cosmetic.hair.none": "Chauve", + "cosmetic.hair.short": "Cheveux courts", + "cosmetic.hair.long": "Cheveux longs", + "cosmetic.hair.ponytail": "Queue de cheval", + "cosmetic.hair.braided": "Tresses", + "cosmetic.hair.cyberpunk": "Cheveux neon cyberpunk", + "cosmetic.hair.fire": "Cheveux en feu", + "cosmetic.hair.stardust": "Coiffure Poussiere d'Etoile legendaire", + "cosmetic.eyes.none": "Pas d'yeux (mysterieux !)", + "cosmetic.eyes.blue": "Yeux bleus", + "cosmetic.eyes.green": "Yeux verts", + "cosmetic.eyes.redorange": "Yeux rouge-orange", + "cosmetic.eyes.brown": "Yeux marron", + "cosmetic.eyes.black": "Yeux noirs", + "cosmetic.eyes.sunglasses": "Lunettes de soleil", + "cosmetic.eyes.pilotglasses": "Lunettes d'aviateur", + "cosmetic.eyes.aircraftglasses": "Lunettes de pilote de chasse", + "cosmetic.eyes.cybernetic": "Yeux cybernetiques", + "cosmetic.eyes.magician": "Lunettes de magicien", + "cosmetic.body.naked": "Torse nu", + "cosmetic.body.regulartshirt": "T-shirt basique", + "cosmetic.body.sexytshirt": "T-shirt sexy", + "cosmetic.body.suit": "Costume", + "cosmetic.body.armored": "Armure", + "cosmetic.body.robotic": "Chassis robotique", + "cosmetic.legs.none": "Flottant (pas de jambes !)", + "cosmetic.legs.naked": "Jambes nues", + "cosmetic.legs.slip": "Slip", + "cosmetic.legs.short": "Short", + "cosmetic.legs.panty": "Culotte", + "cosmetic.legs.rocketboots": "Bottes a reaction", + "cosmetic.legs.pegleg": "Jambe de bois", + "cosmetic.legs.tentacles": "Tentacules", + "cosmetic.arms.none": "Pas de bras (mode T-Rex)", + "cosmetic.arms.short": "Bras courts", + "cosmetic.arms.regular": "Bras normaux", + "cosmetic.arms.long": "Bras longs extensibles", + "cosmetic.arms.mechanical": "Bras mecaniques", + "cosmetic.arms.wings": "Ailes", + "cosmetic.arms.extrapair": "Quatre bras", + + "cosmetic.gender_error": "Nouveau genre (ERREUR : les boites n'ont pas de genre. La boite s'excuse pour la confusion.)", + + "tint.none": "Naturel", + "tint.cyan": "Cyan", + "tint.orange": "Orange", + "tint.purple": "Violet", + "tint.warmpink": "Rose chaud", + "tint.light": "Clair", + "tint.dark": "Sombre", + "tint.rainbow": "Arc-en-ciel", + "tint.neon": "Neon", + "tint.silver": "Argent", + "tint.gold": "Or", + "tint.void": "Neant", + + "material.wood": "Bois", + "material.bronze": "Bronze", + "material.iron": "Fer", + "material.steel": "Acier", + "material.titanium": "Titane", + "material.diamond": "Diamant", + "material.carbonfiber": "Fibre de carbone", + "material.form.raw": "Brut", + "material.form.refined": "Raffine", + "material.form.nail": "Clou", + "material.form.plank": "Planche", + "material.form.ingot": "Lingot", + "material.form.sheet": "Feuille", + "material.form.thread": "Fil", + "material.form.dust": "Poudre", + "material.form.gem": "Gemme", + + "item.health_potion_small": "Petite Potion de Sante", + "item.health_potion_medium": "Potion de Sante Moyenne", + "item.health_potion_large": "Grande Potion de Sante", + "item.mana_crystal_small": "Petit Cristal de Mana", + "item.mana_crystal_medium": "Cristal de Mana Moyen", + "item.food_ration": "Ration alimentaire", + "item.stamina_drink": "Boisson d'endurance", + "item.blood_vial": "Fiole de sang", + "item.gold_pouch": "Bourse d'or", + "item.oxygen_tank": "Reservoir d'oxygene", + "item.energy_cell": "Cellule d'energie", + + "item.space.badge": "Badge d'astronaute", + "item.space.phone": "Numero de telephone alien", + "item.space.key": "Cle d'acces au sas", + "item.space.map": "Carte stellaire", + "item.space.coordinates": "Coordonnees mysterieuses", + "item.space.helmet": "Casque spatial", + "item.medieval.crest": "Blason de chevalier", + "item.medieval.sword": "Replique d'Excalibur", + "item.medieval.scroll": "Parchemin ancien", + "item.medieval.seal": "Sceau royal", + "item.medieval.key": "Cle du donjon", + "item.pirate.map": "Carte au tresor", + "item.pirate.compass": "Boussole enchantee", + "item.pirate.feather": "Plume de perroquet", + "item.pirate.rum": "Bouteille de rhum", + "item.pirate.flag": "Jolly Roger", + "item.pirate.key": "Cle du coffre", + "item.contemporary.phone": "Smartphone", + "item.contemporary.card": "Carte de credit", + "item.contemporary.ticket": "Ticket de metro", + "item.contemporary.usb": "Cle USB suspecte", + "item.contemporary.key": "Cle d'appartement", + "item.contemporary.badge": "Badge d'entreprise", + "item.sentimental.letter": "Lettre d'amour", + "item.sentimental.flower": "Fleur sechee", + "item.sentimental.album": "Album photo", + "item.sentimental.melody": "Melodie de boite a musique", + "item.sentimental.teddy": "Vieil ours en peluche", + "item.sentimental.phone": "Numero de l'ex", + "item.prehistoric.tooth": "Dent de dinosaure", + "item.prehistoric.painting": "Fragment de peinture rupestre", + "item.prehistoric.amber": "Pierre d'ambre", + "item.prehistoric.club": "Massue en os", + "item.prehistoric.fossil": "Fossile de trilobite", + "item.cosmic.shard": "Eclat de nebuleuse", + "item.cosmic.fragment": "Fragment de trou noir", + "item.cosmic.crystal": "Cristal de quasar", + "item.cosmic.dust": "Poussiere cosmique", + "item.cosmic.core": "Coeur d'etoile", + "item.microscopic.bacteria": "Echantillon de bacterie sentiente", + "item.microscopic.dna": "Brin d'ADN luminescent", + "item.microscopic.membrane": "Membrane cellulaire renforcee", + "item.microscopic.mitochondria": "Mitochondrie hyperactive", + "item.microscopic.prion": "Prion amical (probablement)", + "item.darkfantasy.ring": "Anneau maudit", + "item.darkfantasy.rune": "Rune de sang", + "item.darkfantasy.cloak": "Cape d'ombre", + "item.darkfantasy.grimoire": "Grimoire du necromancien", + "item.darkfantasy.gem": "Gemme d'ame", + "item.darkfantasy.key": "Cle en os", + + "item.resource_max_up": "{0} Max +1", + "item.resource_up": "{0} +1", + "item.stat_boost": "{0} +1", + + "item.mysterious_key": "Cle mysterieuse", + "item.mysterious_key.desc": "Une cle pour... quelque chose. La boite sait, mais la boite ne parle pas.", + + "lore.fragment_1": "Au commencement, il y avait une boite. La boite contenait une autre boite. Et c'est ainsi que ca a ete, et que ca sera.", + "lore.fragment_2": "L'Ancien Ordre des Ouvreurs de Boites n'a qu'un seul commandement : Tu ouvriras tes boites.", + "lore.fragment_3": "Certains disent que l'univers lui-meme est une boite, attendant d'etre ouverte par quelqu'un d'assez curieux.", + "lore.fragment_4": "La premiere boite a ete ouverte par Farah, qui a trouve a l'interieur le concept d''interieur'.", + "lore.fragment_5": "Malkith a un jour ouvert une boite contenant le son d'une seule main qui applaudit. Personne ne sait ce que ca veut dire.", + "lore.fragment_6": "La legende dit qu'il existe une boite qui contient toutes les autres boites. L'ouvrir causerait un paradoxe. Ou un remboursement.", + "lore.fragment_7": "Duncan a essaye de fermer une boite un jour. Le syndicat des boites s'est mis en greve pendant trois semaines.", + "lore.fragment_8": "Pierrick a construit une machine a ouvrir des boites. Elle s'est ouverte elle-meme. Puis elle a ouvert la machine. Puis elle a ouvert le concept d'ouverture.", + "lore.fragment_9": "Samuel a ecrit le premier manuel d'ouverture de boites. Chapitre 1 : Ouvre la boite. Chapitre 2 : Voir Chapitre 1.", + "lore.fragment_10": "La Boite Noire contient un chat. Ou pas. Jusqu'a ce que tu l'ouvres, elle en contient et n'en contient pas. Le chat est aussi une boite.", + + "cookie.1": "Une boite dans une boite reste une boite.", + "cookie.2": "ERREUR : Ce cookie ne contient aucune fortune. Reessayez.", + "cookie.3": "Vous ouvrirez beaucoup de boites. Cette prediction a un taux de precision de 100%.", + "cookie.4": "Le vrai tresor, c'etait les boites qu'on a ouvertes en chemin.", + "cookie.5": "ATTENTION : Les effets secondaires de l'ouverture de boites incluent la joie, la confusion et des bras-tentacules.", + "cookie.6": "Demain tu trouveras une boite. Puis une autre. Puis une autre. Envoyez de l'aide.", + "cookie.7": "Ton nombre porte-bonheur est le nombre de boites que tu as ouvertes. Donc... beaucoup.", + "cookie.8": "Confucius dit : celui qui ouvre boite trouve boite. Celui qui n'ouvre pas boite trouve aussi boite. Boite est inevitable.", + "cookie.9": "Un voyage de mille boites commence par une seule ouverture.", + "cookie.10": "Si tu lis ceci, tu as passe trop de temps a ouvrir des boites. Je rigole, ca n'existe pas.", + "cookie.11": "La boite donne, et la boite redonne des boites.", + "cookie.12": "En Russie sovietique, c'est la boite qui t'ouvre.", + "cookie.13": "Au secours je suis piege dans une usine a fortune cookies a l'interieur d'une boite.", + "cookie.14": "Cette fortune a ete intentionnellement laissee vide. Je rigole. Ou pas ?", + "cookie.15": "Tu es l'elu. Celui qui ouvre les boites. Vraiment une noble vocation.", + "cookie.16": "Plot twist : la boite c'etait les amis qu'on s'est faits en chemin.", + "cookie.17": "Schrodinger a appele. Il veut recuperer son concept de boite.", + "cookie.18": "Si tu ouvres une boite et que personne n'est la pour l'entendre, est-ce que ca fait un loot ?", + "cookie.19": "Aujourd'hui est un bon jour pour ouvrir des boites. Demain aussi. Tous les jours, en fait.", + "cookie.20": "Ton animal totem est une boite. Ton pouvoir special c'est l'ouverture.", + + "character.farah": "Farah", + "character.malkith": "Malkith", + "character.linu": "Linu", + "character.chenda": "Chenda", + "character.duncan": "Duncan", + "character.sandrea": "Sandrea", + "character.samuel": "Samuel", + "character.pierrick": "Pierrick", + "character.nova": "Capitaine Nova", + "character.aria": "ARIA", + "character.blackbeard": "Barbe-Noire l'Indeboitable", + "character.mordecai": "Mordecai le Sinistre", + "character.zephyr": "Zephyr", + "character.quantum": "Dr. Quantum", + + "adventure.start": "Commencer l'aventure {0}", + "adventure.resume": "Reprendre l'aventure {0}", + "adventure.completed": "Aventure terminee ! Tu es maintenant un aventurier de boites certifie.", + + "interaction.key_chest": "La cle rentre ! Le coffre s'ouvre automatiquement !", + "interaction.key_no_match": "Cette cle semble ouvrir quelque chose... mais tu ne l'as pas encore. Peut-etre qu'une future boite le fournira.", + "interaction.craft_available": "Nouvelle recette disponible a {0} !", + + "save.saving": "Sauvegarde en cours...", + "save.saved": "Partie sauvegardee dans l'emplacement '{0}'.", + "save.loading": "Chargement...", + "save.loaded": "Partie chargee depuis l'emplacement '{0}'.", + "save.no_saves": "Aucune sauvegarde trouvee.", + "save.choose_slot": "Choisis un emplacement de sauvegarde :", + + "error.invalid_input": "Entree invalide. Reessaie, brave ouvreur de boites.", + "error.no_boxes": "Tu n'as aucune boite a ouvrir. Comment t'as fait ? Ouvre plus de boites pour avoir des boites.", + "error.not_enough_resources": "Pas assez de {0}. Il t'en manque {1}.", + + "misc.boxes_opened": "Total de boites ouvertes : {0}", + "misc.play_time": "Temps de jeu : {0}", + "misc.welcome_back": "Bon retour, {0} ! Tes boites se sont ennuyees." +} diff --git a/docs/GDD.md b/docs/GDD.md new file mode 100644 index 0000000..c2aed35 --- /dev/null +++ b/docs/GDD.md @@ -0,0 +1,932 @@ +# Open The Box - Game Design Document + +> **Version**: 0.1.0-alpha +> **Date**: 2026-03-10 +> **Auteur**: Equipe Open The Box +> **Architecture de reference**: Black Box Sim (Brian Cronin) +> **Plateforme**: CLI (.NET / C#) +> **Localisation**: FR / EN + +--- + +## Table des matieres + +1. [Vue d'ensemble / Concept](#1-vue-densemble--concept) +2. [Mecanique principale : ouverture de boites](#2-mecanique-principale--ouverture-de-boites) +3. [Systeme de progression CLI (Phases 0-8)](#3-systeme-de-progression-cli-phases-0-8) +4. [Systeme de boites](#4-systeme-de-boites) +5. [Auto-activation (cle + coffre, interactions automatiques)](#5-auto-activation-cle--coffre-interactions-automatiques) +6. [Personnalisation](#6-personnalisation) +7. [Materiaux et Craft](#7-materiaux-et-craft) +8. [Ressources](#8-ressources) +9. [Aventures interactives](#9-aventures-interactives) +10. [Personnages](#10-personnages) +11. [Localisation](#11-localisation) + +--- + +## 1. Vue d'ensemble / Concept + +**Open The Box** est un jeu CLI (Command-Line Interface) d'ouverture de boites dans lequel le joueur decouvre progressivement un univers riche en ouvrant des boites contenant des objets, des lieux, des personnages, des cosmetiques et des meta-ameliorations. + +### Pitch + +> *Ouvrez une boite. Elle contient une autre boite. Celle-ci contient une cle. La cle ouvre un coffre. Le coffre contient un fragment d'histoire. L'histoire deverrouille un nouveau personnage. Le personnage revele un lieu. Le lieu offre une aventure. L'aventure recompense... une boite.* + +### Piliers de design + +1. **La curiosite comme moteur** -- Chaque ouverture de boite est une micro-revelation. Le joueur ne sait jamais ce qu'il va trouver, et chaque trouvaille enrichit l'univers du jeu. +2. **Progression de l'interface** -- Le CLI lui-meme evolue au fil du jeu. On commence avec un `Console.ReadLine` basique et on termine avec un layout complet Spectre.Console avec panneaux, couleurs, animations et raccourcis clavier. +3. **Emergent gameplay par combinaison** -- Les objets interagissent entre eux de maniere automatique (auto-activation). Une cle trouvee active automatiquement le coffre correspondant, revelant de nouveaux contenus. +4. **Profondeur cachee** -- Sous l'apparence simple d'un jeu d'ouverture de boites se cache un systeme de craft, d'aventures interactives, de personnalisation et de narration. + +### Architecture Black Box Sim (Brian Cronin) + +Le jeu suit le pattern "Black Box Sim" : + +- **Entree** : une action simple (ouvrir une boite) +- **Traitement** : un systeme opaque de loot tables, conditions, poids et interactions +- **Sortie** : un resultat surprenant et satisfaisant + +Le joueur n'a jamais acces aux probabilites exactes. Il decouvre le systeme par l'experience et l'experimentation. Les regles internes sont volontairement opaques -- d'ou le nom "Black Box". + +Le modele de donnees est centre sur trois entites : +- **Box** (la boite) -- contient une loot table ponderee +- **Item** (l'objet) -- le resultat d'une ouverture +- **Player** (le joueur) -- son inventaire, ses stats, son etat de progression + +--- + +## 2. Mecanique principale : ouverture de boites + +### Boucle de gameplay fondamentale + +``` +[Joueur] -> Ouvre une boite -> [Systeme de loot] -> Obtient un/des objet(s) -> [Auto-activation] -> Effets en chaine -> [Retour a l'inventaire] +``` + +### Deroulement d'une ouverture + +1. Le joueur selectionne une boite dans son inventaire (ou la boite est auto-selectionnee au debut du jeu). +2. Le systeme de loot tire un ou plusieurs objets selon la loot table de la boite. +3. L'objet est ajoute a l'inventaire du joueur. +4. Le systeme d'auto-activation verifie si des interactions sont possibles avec les objets existants. +5. Si des interactions existent, elles sont executees automatiquement (ouverture de coffre, activation de lieu, etc.). +6. L'affichage est mis a jour en fonction du niveau de progression CLI. + +### Premiere ouverture + +Le jeu commence avec une unique **Boite de depart**. Cette boite contient toujours (100%) une **Boite a boite**, qui elle-meme contient d'autres boites selon un systeme de poids detaille en section 4. + +--- + +## 3. Systeme de progression CLI (Phases 0-8) + +Le coeur de l'originalite d'Open The Box : l'interface elle-meme est un systeme de progression. Le joueur commence avec l'interface la plus minimale possible et debloquer des ameliorations d'interface via les **Boites Meta**. + +### Phase 0 -- Console brute + +- **Interface** : `Console.ReadLine` / `Console.WriteLine` uniquement +- **Interaction** : le joueur tape du texte pour agir +- **Visuel** : texte blanc sur fond noir, aucune mise en forme +- **Ressenti** : brut, mysterieux, inconfortable volontairement + +### Phase 1 -- Couleurs de texte + +- **Deblocage** : `UIFeature.TextColors` +- **Ajout** : les objets, noms et quantites s'affichent en couleur +- **Technologie** : codes ANSI basiques (8 couleurs) +- **Impact** : premiere sensation de "progression de l'interface" + +### Phase 2 -- Couleurs etendues + +- **Deblocage** : `UIFeature.ExtendedColors` +- **Ajout** : palette complete de 256 couleurs, degradees et nuances +- **Technologie** : codes ANSI etendus +- **Impact** : le texte devient visuellement plus riche et lisible + +### Phase 3 -- Selection par fleches + +- **Deblocage** : `UIFeature.ArrowKeySelection` +- **Ajout** : le joueur peut naviguer dans les menus avec les fleches directionnelles au lieu de taper des commandes +- **Technologie** : `Console.ReadKey` + gestion de curseur +- **Impact** : amelioration majeure de l'ergonomie + +### Phase 4 -- Panneau d'inventaire + +- **Deblocage** : `UIFeature.InventoryPanel` +- **Ajout** : un panneau lateral affiche l'inventaire du joueur en permanence +- **Technologie** : Spectre.Console `Table` ou `Panel` +- **Impact** : le joueur voit enfin son inventaire sans taper de commande + +### Phase 5 -- Panneaux de ressources et stats + +- **Deblocage** : `UIFeature.ResourcePanel` + `UIFeature.StatsPanel` +- **Ajout** : barres de ressources (HP, Mana, Food, etc.) et statistiques du joueur +- **Technologie** : Spectre.Console `BarChart` et `Table` +- **Impact** : le jeu ressemble desormais a un vrai RPG en CLI + +### Phase 6 -- Portrait et chat + +- **Deblocage** : `UIFeature.PortraitPanel` + `UIFeature.ChatPanel` +- **Ajout** : affichage ASCII du portrait du joueur (base sur les cosmetiques equipes) + panneau de chat avec les PNJ +- **Technologie** : Spectre.Console `Canvas` ou `Markup` avance +- **Impact** : dimension sociale et visuelle + +### Phase 7 -- Animations et craft + +- **Deblocage** : `UIFeature.BoxAnimation` + `UIFeature.CraftingPanel` +- **Ajout** : animations d'ouverture de boites (texte defilant, effets visuels ASCII) + panneau de craft +- **Technologie** : Spectre.Console `Live` et `Status` +- **Impact** : le jeu devient spectaculaire et immersif + +### Phase 8 -- Full Layout + +- **Deblocage** : `UIFeature.FullLayout` + `UIFeature.KeyboardShortcuts` +- **Ajout** : mise en page complete avec tous les panneaux organises, raccourcis clavier pour toutes les actions +- **Technologie** : Spectre.Console `Layout` complet +- **Impact** : l'interface finale, riche et complete -- la recompense ultime pour la progression + +### Tableau recapitulatif + +| Phase | UIFeature(s) | Technologie principale | +|-------|-------------------------------------|-----------------------------| +| 0 | *(aucun)* | Console.ReadLine | +| 1 | TextColors | ANSI 8 couleurs | +| 2 | ExtendedColors | ANSI 256 couleurs | +| 3 | ArrowKeySelection | Console.ReadKey | +| 4 | InventoryPanel | Spectre.Console Panel/Table | +| 5 | ResourcePanel, StatsPanel | Spectre.Console BarChart | +| 6 | PortraitPanel, ChatPanel | Spectre.Console Canvas | +| 7 | BoxAnimation, CraftingPanel | Spectre.Console Live/Status | +| 8 | FullLayout, KeyboardShortcuts | Spectre.Console Layout | + +--- + +## 4. Systeme de boites + +### Hierarchie complete + +Le systeme de boites est le coeur du jeu. Chaque boite contient une **loot table** ponderee. Les boites peuvent contenir d'autres boites, creant un systeme recursif et emergent. + +### Boite de depart + +> *La toute premiere boite du jeu.* + +| Contenu | Poids | Notes | +|----------------------|-------|---------------------------| +| Boite a boite | 100% | Toujours garantie | + +La Boite de depart est unique. Elle n'apparait qu'une seule fois au tout debut du jeu. + +### Boite a boite + +> *La boite qui contient des boites. Le nexus central du jeu.* + +La Boite a boite est le hub de distribution principal. Elle contient une selection ponderee parmi toutes les sous-boites disponibles du jeu. + +| Contenu | Poids | Conditions | +|------------------------|--------|-----------------------------------------| +| Boite Meta | 20 | *(toujours disponible)* | +| Boite stylee | 15 | *(toujours disponible)* | +| Boite aventure | 15 | *(toujours disponible)* | +| Boite d'amelioration | 12 | Si ressources OU equipement > 0 | +| Boite de fourniture | 12 | Si ressources > 0 | +| Boite noire | 8 | *(toujours disponible)* | +| Boite a histoire | 6 | *(toujours disponible)* | +| Boite a musique | 5 | *(toujours disponible)* | +| Boite a Cookies | 4 | *(toujours disponible)* | +| Boite legendaire | 2 | *(toujours disponible)* | +| Boite legend'hair | 1 | *(toujours disponible)* | + +> **Note** : Les poids sont relatifs. Le total n'est pas forcement 100 -- les poids sont normalises au moment du tirage. Les boites conditionnelles sont exclues du tirage si la condition n'est pas remplie. + +--- + +### Boite Meta + +> *La boite qui ameliore le jeu lui-meme.* + +La Boite Meta contient des deblocages d'interface (UIFeatures) et des ameliorations meta du jeu. Elle est essentielle a la progression du joueur car c'est le seul moyen d'ameliorer l'interface CLI. + +| Contenu | Poids | Notes | +|----------------------------------|-------|------------------------------------------| +| UIFeature (prochain deblocage) | 60 | Deverrouille la prochaine phase CLI | +| Badge de progression | 15 | Badge commemoratif | +| Font deblocable | 10 | Nouvelle police pour l'interface | +| TextColor (nouveau slot) | 10 | Personnalisation des couleurs de texte | +| Boite Meta (recursion) | 5 | 5% de chance d'obtenir une autre Boite Meta | + +Le systeme de deblocage des UIFeatures est **sequentiel** : on debloque toujours la prochaine phase dans l'ordre (Phase 1, puis 2, puis 3, etc.). Si toutes les phases sont deja debloquees, le poids de UIFeature est redistribue aux autres entrees. + +--- + +### Boite stylee + +> *La boite de la mode et du style.* + +La Boite stylee contient des cosmetiques pour personnaliser l'apparence du joueur. + +| Contenu | Poids | Notes | +|--------------------------|-------|--------------------------------------------------------| +| Coiffure (HairStyle) | 25 | Style de cheveux aleatoire | +| Yeux (EyeStyle) | 20 | Style d'yeux aleatoire | +| Corps (BodyStyle) | 15 | Style de corps aleatoire | +| Jambes (LegStyle) | 15 | Style de jambes aleatoire | +| Bras (ArmStyle) | 10 | Style de bras aleatoire | +| Teinture (TintColor) | 10 | Couleur de teinture applicable a n'importe quel slot | +| Nouveau genre | 5 | **ERROR: les boites n'ont pas de genre.** | + +> **Note speciale sur "Nouveau genre"** : Cette entree est un easter egg volontaire. Si le joueur tombe sur "Nouveau genre", le systeme affiche un message d'erreur humoristique : `"ERROR: les boites n'ont pas de genre."` et le joueur ne recoit rien -- sauf un Badge special "Genre Error" s'il ne l'a pas deja. + +--- + +### Boite legend'hair + +> *La boite legendaire... capillaire.* + +| Contenu | Poids | Notes | +|----------------------------------|-------|------------------------------------------| +| Coiffure Legendaire (garantie) | 100 | Tire parmi les coiffures de rarete Legendaire ou Mythic | + +La Boite legend'hair garantit **toujours** une coiffure de rarete Legendaire ou superieure. C'est la boite la plus rare de la Boite a boite (poids 1). + +Coiffures legendaires disponibles : +- **StardustLegendary** -- des cheveux faits de poussiere d'etoile, brillants et changeants +- **Fire** -- des cheveux de flammes vivantes +- **Cyberpunk** -- coiffure high-tech avec effets neon + +--- + +### Boite legendaire + +> *La boite du prestige absolu.* + +| Contenu | Poids | Notes | +|----------------------------------|-------|------------------------------------------| +| Coiffure Legendaire | 30 | Identique a Boite legend'hair | +| Objet Legendaire | 25 | Item de rarete Legendaire (arme, armure) | +| Personnage Legendaire | 20 | Personnage rare et puissant | +| Lieu Legendaire | 15 | Lieu unique et memorable | +| Blueprint Legendaire | 10 | Blueprint de station de craft rare | + +Contrairement a la Boite legend'hair (garantie capillaire), la Boite legendaire offre un objet legendaire dans n'importe quelle categorie. + +--- + +### Boite aventure + +> *La porte vers neuf mondes differents.* + +La Boite aventure contient un **jeton d'aventure** (AdventureToken) qui deverrouille une aventure interactive dans un des 9 themes du jeu. + +| Contenu | Poids | Notes | +|----------------------------------|-------|------------------------------------------| +| Boite aventure Space | 14 | Aventure spatiale | +| Boite aventure Medieval | 14 | Aventure medievale | +| Boite aventure Pirate | 14 | Aventure pirate | +| Boite aventure Contemporary | 12 | Aventure contemporaine | +| Boite aventure Sentimental | 10 | Aventure sentimentale / romance | +| Boite aventure Prehistoric | 10 | Aventure prehistorique | +| Boite aventure Cosmic | 10 | Aventure cosmique | +| Boite aventure Microscopic | 8 | Aventure microscopique | +| Boite aventure DarkFantasy | 8 | Aventure dark fantasy | + +Chaque sous-boite d'aventure contient : +- 1 AdventureToken du theme correspondant +- 1 objet thematique (arme, armure ou consommable lie au theme) +- Chance de : 1 personnage thematique (20%), 1 fragment de lore (30%) + +--- + +### Boite d'amelioration + +> *La boite qui rend plus fort.* + +**Condition d'apparition** : le joueur doit posseder au moins une ressource OU un equipement dans son inventaire. Si cette condition n'est pas remplie, la Boite d'amelioration est exclue de la loot table de la Boite a boite. + +| Contenu | Poids | Notes | +|----------------------------------|-------|------------------------------------------| +| Amelioration de ressource max | 35 | +10% au cap d'une ressource aleatoire | +| Amelioration d'equipement | 25 | +1 niveau a un equipement possede | +| Amelioration de stat | 20 | +1 a une statistique aleatoire | +| Blueprint de station | 15 | Plan pour construire une nouvelle station | +| Amelioration de station | 5 | +1 niveau a une station existante | + +--- + +### Boite de fourniture + +> *La boite du ravitaillement.* + +**Condition d'apparition** : le joueur doit posseder au moins une ressource active (debloquee). Si le joueur n'a encore aucune ressource, cette boite n'apparait pas. + +| Contenu | Poids | Notes | +|----------------------------------|-------|------------------------------------------| +| Health (soin) | 20 | Restaure des points de vie | +| Mana | 15 | Restaure des points de mana | +| Food (nourriture) | 20 | Restaure de la nourriture | +| Stamina (endurance) | 15 | Restaure de l'endurance | +| Gold (or) | 15 | Ajoute de l'or | +| Energy (energie) | 10 | Restaure de l'energie | +| Blood (sang) | 3 | Restaure du sang (ressource rare) | +| Oxygen (oxygene) | 2 | Restaure de l'oxygene (ressource rare) | + +> **Note** : Seules les ressources deja debloquees par le joueur peuvent apparaitre dans le tirage. Les poids sont recalcules dynamiquement. + +--- + +### Boite noire + +> *La boite mysterieuse. Personne ne sait ce qu'elle contient avant de l'ouvrir.* + +La Boite noire est speciale : son contenu est totalement aleatoire et peut provenir de **n'importe quelle** autre loot table du jeu. Elle peut contenir : + +- Un objet de n'importe quelle rarete (Common a Mythic) +- Un cosmetique de n'importe quel slot +- Un materiau de n'importe quel type et forme +- Un personnage +- Un fragment de lore +- Un token d'aventure +- Une autre boite (y compris une autre Boite noire, dans de tres rares cas) + +La Boite noire est le symbole meme du principe "Black Box" du jeu : l'opacite totale. + +--- + +### Boite a histoire + +> *La boite qui raconte.* + +| Contenu | Poids | Notes | +|----------------------------------|-------|------------------------------------------| +| Fragment de Lore | 50 | Un morceau d'histoire du monde | +| Story Item | 25 | Un objet lie a la narration | +| Personnage (rencontre) | 15 | Un nouveau PNJ se revele | +| Lieu (decouverte) | 10 | Un nouveau lieu est decouvert | + +Les fragments de lore se collectionnent et, une fois assembles, revelent des pans entiers de l'histoire du monde d'Open The Box. + +--- + +### Boite a musique + +> *La boite qui chante.* + +| Contenu | Poids | Notes | +|----------------------------------|-------|------------------------------------------| +| Musique d'ambiance | 40 | Theme musical pour un lieu ou une aventure | +| Jingle d'ouverture | 25 | Son joue a l'ouverture de boite | +| Theme de personnage | 20 | Musique associee a un PNJ | +| Boite a musique rare | 10 | Musique rare ou legendary | +| Easter egg sonore | 5 | Son cache, reference culturelle | + +> **Note technique** : les "musiques" sont representees par des descriptions textuelles et des patterns ASCII art dans la console. Si le systeme audio est disponible, elles peuvent aussi etre jouees. + +--- + +### Boite a Cookies + +> *La boite des petits plaisirs.* + +| Contenu | Poids | Notes | +|----------------------------------|-------|------------------------------------------| +| Cookie de fortune | 40 | Message aleatoire (humoristique ou sage) | +| Cookie de buff | 25 | Buff temporaire (+10% chance de rare) | +| Cookie de craft | 15 | Accelere le prochain craft | +| Cookie dore | 10 | Effet rare et memorable | +| Cookie d'XP | 7 | Bonus d'experience | +| Cookie meta | 3 | Effet meta-game (change une regle) | + +--- + +## 5. Auto-activation (cle + coffre, interactions automatiques) + +### Principe + +L'auto-activation est un systeme central d'Open The Box. Lorsqu'un objet est ajoute a l'inventaire du joueur, le systeme verifie automatiquement si cet objet peut interagir avec un autre objet deja present. + +### Types d'interactions automatiques + +| Interaction | Declencheur | Resultat | +|---------------------------|--------------------------------------|-------------------------------------| +| **Cle + Coffre** | Cle et coffre correspondants | Le coffre s'ouvre, revelant son contenu | +| **Carte + Lieu** | Carte d'un lieu + badge d'exploration| Le lieu est deverrouille | +| **Token + Aventure** | AdventureToken + seuil de progression| L'aventure devient accessible | +| **Blueprint + Materiaux** | Blueprint + materiaux requis | La station de craft est construite | +| **Fragment + Fragment** | Fragments complementaires | Un lore complet est revele | +| **Consommable + Ressource** | Consommable + ressource cible | La ressource est restauree | + +### Flux d'auto-activation + +``` +1. Objet ajoute a l'inventaire +2. Pour chaque objet de l'inventaire : + a. Verifier si une interaction est possible avec le nouvel objet + b. Si oui, executer l'interaction + c. L'interaction peut produire de nouveaux objets + d. Si de nouveaux objets sont produits, repeter depuis l'etape 2 +3. Fin de la chaine d'auto-activation +``` + +### Chaines d'activation + +Les auto-activations peuvent creer des **chaines** : ouvrir un coffre peut reveler une cle qui ouvre un autre coffre, qui contient un fragment qui complete un lore, qui debloque un personnage. Ces chaines sont l'un des moments les plus satisfaisants du jeu. + +### Type de resultat d'interaction (InteractionResultType) + +Chaque interaction produit un resultat type parmi : +- **OpenBox** -- ouverture d'une boite ou d'un coffre +- **Craft** -- fabrication d'un objet +- **Transform** -- transformation d'un objet en un autre +- **Consume** -- consommation d'un objet (potion, nourriture) +- **Unlock** -- deblocage d'un contenu (lieu, aventure, personnage) +- **Combine** -- combinaison de plusieurs objets en un seul +- **Teleport** -- deplacement vers un nouveau lieu + +--- + +## 6. Personnalisation + +### Slots cosmetiques + +Le joueur peut personnaliser l'apparence de son avatar a travers 5 slots cosmetiques : + +#### Coiffure (CosmeticSlot.Hair) + +| Style | Rarete | Description | +|-------------------|-------------|----------------------------------------------------| +| None | -- | Pas de coiffure (defaut) | +| Short | Common | Cheveux courts classiques | +| Long | Common | Cheveux longs | +| Ponytail | Uncommon | Queue de cheval | +| Braided | Rare | Tresses elaborees | +| Cyberpunk | Epic | Coiffure high-tech avec effets neon | +| Fire | Legendary | Cheveux de flammes vivantes | +| StardustLegendary | Mythic | Cheveux de poussiere d'etoile | + +#### Yeux (CosmeticSlot.Eyes) + +| Style | Rarete | Description | +|-------------------|-------------|----------------------------------------------------| +| None | -- | Pas de style d'yeux (defaut) | +| Blue | Common | Yeux bleus | +| Green | Common | Yeux verts | +| RedOrange | Uncommon | Yeux rouge-orange | +| Brown | Common | Yeux marron | +| Black | Uncommon | Yeux noirs profonds | +| Sunglasses | Rare | Lunettes de soleil | +| PilotGlasses | Rare | Lunettes d'aviateur | +| AircraftGlasses | Epic | Lunettes de pilote de chasse | +| CyberneticEyes | Legendary | Yeux cybernetiques lumineux | +| MagicianGlasses | Epic | Lunettes de magicien (rondes, dorees) | + +#### Corps (CosmeticSlot.Body) + +| Style | Rarete | Description | +|-------------------|-------------|----------------------------------------------------| +| Naked | Common | Torse nu (defaut) | +| RegularTShirt | Common | T-shirt classique | +| SexyTShirt | Uncommon | T-shirt echancre | +| Suit | Rare | Costume elegant | +| Armored | Epic | Armure complete | +| Robotic | Legendary | Corps robotique | + +#### Jambes (CosmeticSlot.Legs) + +| Style | Rarete | Description | +|-------------------|-------------|----------------------------------------------------| +| None | -- | Aucun style (defaut) | +| Naked | Common | Jambes nues | +| Slip | Common | Sous-vetement | +| Short | Uncommon | Short | +| Panty | Uncommon | Collants | +| RocketBoots | Epic | Bottes a reaction | +| PegLeg | Rare | Jambe de bois (pirate) | +| Tentacles | Legendary | Tentacules a la place des jambes | + +#### Bras (CosmeticSlot.Arms) + +| Style | Rarete | Description | +|-------------------|-------------|----------------------------------------------------| +| None | -- | Aucun style (defaut) | +| Short | Common | Bras courts | +| Regular | Common | Bras normaux | +| Long | Uncommon | Bras longs | +| Mechanical | Epic | Bras mecaniques | +| Wings | Legendary | Ailes a la place des bras | +| ExtraPair | Mythic | Paire de bras supplementaire (4 bras au total) | + +### Teintures (TintColor) + +Les teintures sont applicables a **n'importe quel** slot cosmetique. Elles modifient la couleur de l'objet cosmetique equipe. + +| Teinture | Rarete | Effet visuel | +|-----------|-----------|-------------------------------------------| +| None | -- | Couleur d'origine | +| Cyan | Common | Teinte cyan | +| Orange | Common | Teinte orange | +| Purple | Uncommon | Teinte violette | +| WarmPink | Uncommon | Teinte rose chaud | +| Light | Common | Version eclaircie | +| Dark | Common | Version assombrie | +| Rainbow | Epic | Multicolore arc-en-ciel | +| Neon | Rare | Effet neon brillant | +| Silver | Rare | Teinte argentee metallique | +| Gold | Epic | Teinte doree | +| Void | Legendary | Noir absolu avec reflets d'etoiles | + +--- + +## 7. Materiaux et Craft + +### Materiaux (7 types) + +Le systeme de craft repose sur 7 types de materiaux, classes par ordre de rarete et de puissance : + +| Materiau | Rarete d'apparition | Tier | Description | +|---------------|---------------------|------|--------------------------------------| +| Wood | Common | 1 | Bois, le materiau le plus basique | +| Bronze | Common | 2 | Bronze, alliage simple | +| Iron | Uncommon | 3 | Fer, materiau intermediaire | +| Steel | Rare | 4 | Acier, materiau avance | +| Titanium | Epic | 5 | Titane, materiau de haute technologie| +| Diamond | Legendary | 6 | Diamant, materiau precieux | +| CarbonFiber | Mythic | 7 | Fibre de carbone, materiau ultime | + +### Formes de materiaux (9 formes) + +Chaque materiau peut exister sous differentes formes, obtenues par transformation dans les stations de craft : + +| Forme | Description | Station typique | +|----------|------------------------------------------|--------------------------| +| Raw | Materiau brut, non transforme | *(aucune)* | +| Refined | Materiau raffine, pret a l'emploi | Foundry | +| Nail | Clou, pour la construction | Anvil | +| Plank | Planche (surtout pour le bois) | SawingPost | +| Ingot | Lingot (surtout pour les metaux) | Furnace | +| Sheet | Feuille ou plaque fine | Forge | +| Thread | Fil (pour le tissage et la couture) | Loom | +| Dust | Poudre (pour l'alchimie) | MortarAndPestle | +| Gem | Gemme taillee (pour la joaillerie) | Jewelry | + +### Combinatoire + +Avec 7 materiaux et 9 formes, le systeme offre **63 combinaisons** possibles de materiaux (7 x 9 = 63). Toutes les combinaisons ne sont pas forcement accessibles immediatement -- certaines necessitent des stations de craft specifiques. + +### Stations de craft (30+ stations) + +Les stations de craft sont debloquees via des **Blueprints** (WorkstationBlueprint) trouves dans les boites. Chaque station permet de transformer des materiaux et de fabriquer des objets. + +| Station | Description | Specialite | +|------------------------|-------------------------------------------------------|-----------------------------| +| Foundry | Fonderie pour raffiner les materiaux bruts | Raw -> Refined | +| Workbench | Etabli polyvalent | Craft general | +| Furnace | Four pour creer des lingots | Refined -> Ingot | +| Loom | Metier a tisser | Refined -> Thread | +| Anvil | Enclume pour forger clous et outils | Ingot -> Nail, outils | +| AlchemyTable | Table d'alchimie | Potions, elixirs | +| Forge | Forge avancee | Ingot -> Sheet, armes | +| SawingPost | Poste de sciage | Wood -> Plank | +| Windmill | Moulin a vent | Transformation de grains | +| Watermill | Moulin a eau | Transformation hydraulique | +| OilPress | Presse a huile | Production d'huiles | +| PotteryWorkshop | Atelier de poterie | Objets en ceramique | +| TailorTable | Table de couturier | Vetements, cosmetiques | +| MortarAndPestle | Mortier et pilon | Refined -> Dust | +| DyeBasin | Bassin de teinture | Teintures | +| Jewelry | Atelier de joaillerie | Refined -> Gem, bijoux | +| Smoker | Fumoir | Conservation de nourriture | +| BrewingVat | Cuve de brassage | Boissons, potions | +| EngineerDesk | Bureau d'ingenieur | Blueprints avances | +| WeldingStation | Poste de soudure | Assemblage metal | +| DrawingTable | Table a dessin | Plans et schemas | +| EngravingBench | Banc de gravure | Gravure et enchantement | +| SewingPost | Poste de couture | Thread -> vetements | +| MagicCauldron | Chaudron magique | Potions magiques puissantes | +| TransformationPentacle | Pentacle de transformation | Transformation d'objets | +| PaintingSpace | Espace de peinture | Art et decoration | +| Distillery | Distillerie | Alcools, essences | +| Printer3D | Imprimante 3D | Objets complexes modernes | +| MatterSynthesizer | Synthetiseur de matiere | Creation de materiaux rares | +| GeneticModStation | Station de modification genetique | Modifications biologiques | +| TemporalBracelet | Bracelet temporel | Manipulation du temps | +| StasisChamber | Chambre de stase | Conservation et evolution | + +--- + +## 8. Ressources + +Le joueur gere 8 ressources qui influencent sa capacite a explorer, combattre, crafter et survivre. + +| Ressource | Icone | Description | Utilisation principale | +|-----------|-------|--------------------------------------------------|--------------------------------| +| Health | HP | Points de vie | Survie, combat | +| Mana | MP | Points de magie | Sorts, enchantements | +| Food | FD | Nourriture | Exploration, stamina | +| Stamina | ST | Endurance | Actions physiques, craft | +| Blood | BL | Sang | Rituels, magie noire | +| Gold | GD | Or | Commerce, ameliorations | +| Oxygen | O2 | Oxygene | Exploration spatiale/sous-marine| +| Energy | EN | Energie | Machines, stations de craft | + +### Mecaniques de ressources + +- Chaque ressource a un **maximum** (cap) qui peut etre ameliore via la Boite d'amelioration. +- Les ressources se consomment lors des aventures, du craft et des interactions. +- Les ressources se restaurent via la Boite de fourniture, les consommables et certaines interactions. +- Certaines ressources sont conditionnelles : **Blood** n'apparait qu'avec le theme DarkFantasy, **Oxygen** qu'avec le theme Space ou des aventures sous-marines. + +### Deblocage des ressources + +Les ressources ne sont pas toutes disponibles des le debut : + +| Ressource | Condition de deblocage | +|-----------|-----------------------------------------------------| +| Health | Debloquee des le debut | +| Mana | Premiere rencontre avec un personnage magique | +| Food | Premiere aventure exploree | +| Stamina | Premiere action physique (combat, craft) | +| Blood | Deblocage du theme DarkFantasy | +| Gold | Premier lieu de commerce decouvert | +| Oxygen | Deblocage du theme Space OU aventure sous-marine | +| Energy | Premiere station de craft construite | + +--- + +## 9. Aventures interactives + +### Les 9 themes + +Les aventures interactives sont des sequences narratives jouables en CLI, chacune avec son propre univers, ses personnages et ses defis. + +#### 1. Space (Espace) + +- **Ambiance** : science-fiction, exploration spatiale +- **Lieux typiques** : vaisseaux spatiaux, stations orbitales, planetes alien +- **Ressource cle** : Oxygen +- **Personnages associes** : Farah (pilote), Samuel (ingenieur) +- **Mood possible** : Investigation, Spooky +- **Environnement** : Nature (planetes), Town (stations) + +#### 2. Medieval (Medieval) + +- **Ambiance** : fantasy medievale, chateaux et donjons +- **Lieux typiques** : chateaux, forets enchantees, villages +- **Ressource cle** : Mana, Stamina +- **Personnages associes** : Malkith (chevalier), Linu (magicienne) +- **Mood possible** : Tragedy, Romance +- **Environnement** : Nature, Town + +#### 3. Pirate + +- **Ambiance** : age d'or de la piraterie +- **Lieux typiques** : navires, iles au tresor, ports +- **Ressource cle** : Gold, Stamina +- **Personnages associes** : Duncan (capitaine), Chenda (navigatrice) +- **Mood possible** : Comedy, Investigation +- **Environnement** : Water, Town + +#### 4. Contemporary (Contemporain) + +- **Ambiance** : monde moderne, thriller urbain +- **Lieux typiques** : villes, bureaux, appartements +- **Ressource cle** : Energy, Gold +- **Personnages associes** : Sandrea (journaliste), Pierrick (hacker) +- **Mood possible** : Investigation, Comedy +- **Environnement** : Town + +#### 5. Sentimental + +- **Ambiance** : romance, relations humaines, emotions +- **Lieux typiques** : parcs, cafes, plages au coucher du soleil +- **Ressource cle** : Health (emotionnel), Mana (intuition) +- **Mood possible** : Romance, Tragedy, Comedy +- **Environnement** : Nature, Town + +#### 6. Prehistoric (Prehistorique) + +- **Ambiance** : ere prehistorique, survie primitive +- **Lieux typiques** : grottes, jungles, volcans +- **Ressource cle** : Food, Stamina +- **Personnages associes** : personnages primitifs +- **Mood possible** : Dark, Comedy +- **Environnement** : Nature + +#### 7. Cosmic (Cosmique) + +- **Ambiance** : echelle cosmique, entites divines, dimensions paralleles +- **Lieux typiques** : nebuleuses, trous noirs, dimensions alternatives +- **Ressource cle** : Mana, Energy +- **Mood possible** : Spooky, Dark +- **Environnement** : Nature (cosmique) + +#### 8. Microscopic (Microscopique) + +- **Ambiance** : monde microscopique, cellules, atomes +- **Lieux typiques** : interieur d'un corps humain, molecules, circuits +- **Ressource cle** : Energy, Oxygen +- **Mood possible** : Investigation, Spooky +- **Environnement** : Nature (biologique) + +#### 9. DarkFantasy (Dark Fantasy) + +- **Ambiance** : fantasy sombre, horreur gothique +- **Lieux typiques** : cryptes, forets maudites, tours sombres +- **Ressource cle** : Blood, Mana +- **Personnages associes** : Malkith (version sombre) +- **Mood possible** : Dark, Spooky, Tragedy +- **Environnement** : Nature, Town + +### Structure d'une aventure + +Chaque aventure suit cette structure : + +1. **Introduction** -- mise en contexte narrative +2. **Exploration** -- le joueur decouvre des lieux et interagit avec l'environnement +3. **Rencontres** -- dialogues avec des PNJ, choix narratifs +4. **Defi** -- combat, enigme ou epreuve +5. **Resolution** -- conclusion de l'aventure avec recompenses + +### Recompenses d'aventure + +- Objets thematiques (armes, armures, cosmetiques du theme) +- Materiaux rares +- Fragments de lore +- Boites speciales (chance de Boite legendaire) +- Deblocage de personnages + +--- + +## 10. Personnages + +### Personnages principaux + +Les personnages sont des PNJ que le joueur rencontre et debloque au fil du jeu. Chaque personnage a sa personnalite, son histoire et ses liens avec les themes d'aventure. + +#### Farah + +- **Role** : Pilote spatiale, exploratrice +- **Theme principal** : Space +- **Personnalite** : courageuse, optimiste, impulsive +- **Lien** : guide le joueur dans les aventures spatiales +- **Quete personnelle** : retrouver son vaisseau perdu + +#### Malkith + +- **Role** : Chevalier, gardien +- **Theme principal** : Medieval / DarkFantasy +- **Personnalite** : honneur, loyaute, tourmente interieure +- **Lien** : protege le joueur dans les aventures medievales et sombres +- **Quete personnelle** : racheter une faute passee + +#### Linu + +- **Role** : Magicienne, erudite +- **Theme principal** : Medieval / Cosmic +- **Personnalite** : curieuse, enigmatique, bienveillante +- **Lien** : enseigne la magie et les mysteres cosmiques au joueur +- **Quete personnelle** : dechiffrer un grimoire ancien + +#### Chenda + +- **Role** : Navigatrice, cartographe +- **Theme principal** : Pirate +- **Personnalite** : pragmatique, debrouillarde, loyale +- **Lien** : guide le joueur sur les mers et dans les explorations +- **Quete personnelle** : cartographier le monde entier + +#### Duncan + +- **Role** : Capitaine pirate, aventurier +- **Theme principal** : Pirate +- **Personnalite** : charismatique, audacieux, impredictible +- **Lien** : leader naturel, entraine le joueur dans des peripeties +- **Quete personnelle** : trouver le tresor ultime + +#### Sandrea + +- **Role** : Journaliste d'investigation +- **Theme principal** : Contemporary +- **Personnalite** : tenace, intelligente, ethique +- **Lien** : devoile les mysteres du monde contemporain +- **Quete personnelle** : reveler une conspiration mondiale + +#### Samuel + +- **Role** : Ingenieur, inventeur +- **Theme principal** : Space / Contemporary +- **Personnalite** : methodique, creatif, reserve +- **Lien** : construit et ameliore les equipements du joueur +- **Quete personnelle** : creer l'invention qui changera le monde + +#### Pierrick + +- **Role** : Hacker, specialiste techno +- **Theme principal** : Contemporary / Cosmic +- **Personnalite** : cynique, brillant, anti-conformiste +- **Lien** : deverrouille les secrets technologiques et numeriques +- **Quete personnelle** : hacker la realite elle-meme + +### Personnages secondaires + +Au-dela des 8 personnages principaux, le jeu contient de nombreux PNJ secondaires : + +- **Marchands** -- vendent et achetent des objets contre de l'or +- **Gardiens de lieux** -- protegent l'acces aux lieux importants +- **Conteurs** -- revelent des fragments de lore +- **Artisans** -- gerent les stations de craft +- **Guides d'aventure** -- accompagnent le joueur dans les aventures thematiques +- **Enigmatiques** -- personnages mysterieux qui apparaissent aleatoirement + +### Relations entre personnages + +Les personnages ont des relations entre eux qui influencent les dialogues et les aventures : + +- **Farah & Samuel** -- collaboration professionnelle, amitie +- **Malkith & Linu** -- respect mutuel, mentor/eleve +- **Duncan & Chenda** -- capitaine/navigatrice, confiance totale +- **Sandrea & Pierrick** -- journaliste/hacker, alliance tactique +- **Malkith (Medieval) & Malkith (DarkFantasy)** -- deux facettes du meme personnage + +--- + +## 11. Localisation + +### Langues supportees + +| Code | Langue | Statut | +|------|----------|-------------| +| FR | Francais | Principal | +| EN | Anglais | Secondaire | + +### Strategie de localisation + +- **Texte de l'interface** : localise via des fichiers de ressources (`.resx` ou JSON) +- **Noms des objets** : localises (ex: "Epee en acier" / "Steel Sword") +- **Noms des personnages** : non localises (les noms propres restent identiques) +- **Fragments de lore** : localises integralement (textes narratifs) +- **Aventures** : dialogues et descriptions localises +- **Enums et cles internes** : en anglais (code source) + +### Exemples de localisation + +| Cle | FR | EN | +|-------------------------|------------------------------|-----------------------------| +| `ui.open_box` | Ouvrir la boite | Open the box | +| `ui.inventory` | Inventaire | Inventory | +| `item.steel_sword` | Epee en acier | Steel Sword | +| `resource.health` | Points de vie | Health Points | +| `adventure.space.intro` | L'espace infini s'ouvre... | The infinite space opens... | +| `box.starter` | Boite de depart | Starter Box | +| `box.meta` | Boite Meta | Meta Box | +| `box.stylish` | Boite stylee | Stylish Box | +| `error.gender` | ERROR: les boites n'ont pas de genre. | ERROR: boxes don't have a gender. | + +--- + +## Annexe A : Glossaire + +| Terme | Definition | +|----------------------|----------------------------------------------------------------------------| +| Black Box Sim | Pattern de design ou les mecaniques internes sont opaques pour le joueur | +| Loot table | Table de probabilites definissant le contenu d'une boite | +| Auto-activation | Interaction automatique entre objets de l'inventaire | +| UIFeature | Fonctionnalite d'interface debloquable | +| Blueprint | Plan permettant de construire une station de craft | +| Fragment de lore | Morceau d'histoire collectible | +| AdventureToken | Jeton d'acces a une aventure thematique | +| Teinture | Couleur applicable a un cosmetique | +| Boite a boite | Boite centrale contenant d'autres boites | +| Boite noire | Boite au contenu totalement aleatoire | + +## Annexe B : Conditions de loot (LootConditionType) + +Les conditions de loot controlent la disponibilite des objets dans les loot tables : + +| Condition | Description | +|--------------------|----------------------------------------------------------| +| HasItem | Le joueur possede un objet specifique | +| HasNotItem | Le joueur ne possede PAS un objet specifique | +| ResourceAbove | Une ressource est au-dessus d'un seuil | +| ResourceBelow | Une ressource est en-dessous d'un seuil | +| BoxesOpenedAbove | Le nombre total de boites ouvertes depasse un seuil | +| HasUIFeature | Le joueur a debloque une fonctionnalite d'interface | +| HasWorkstation | Le joueur possede une station de craft specifique | +| HasAdventure | Le joueur a acces a un theme d'aventure | +| HasCosmetic | Le joueur possede un cosmetique specifique | + +## Annexe C : Stats du joueur + +| Stat | Description | Impact | +|----------------|------------------------------------------------|--------------------------------------| +| Strength | Force physique | Degats melee, capacite de transport | +| Intelligence | Intelligence | Degats magiques, efficacite de craft | +| Luck | Chance | Probabilites de loot ameliorees | +| Charisma | Charisme | Prix des marchands, dialogues | +| Dexterity | Dexterite | Esquive, vitesse, precision | +| Wisdom | Sagesse | Regeneration de mana, intuition | + +--- + +*Fin du Game Design Document -- Open The Box v0.1.0-alpha* diff --git a/lib/Loreline.deps.json b/lib/Loreline.deps.json new file mode 100644 index 0000000..5b5cecd --- /dev/null +++ b/lib/Loreline.deps.json @@ -0,0 +1,24 @@ +{ + "runtimeTarget": { + "name": ".NETStandard,Version=v2.1/", + "signature": "" + }, + "compilationOptions": {}, + "targets": { + ".NETStandard,Version=v2.1": {}, + ".NETStandard,Version=v2.1/": { + "Loreline/1.0.0": { + "runtime": { + "Loreline.dll": {} + } + } + } + }, + "libraries": { + "Loreline/1.0.0": { + "type": "project", + "serviceable": false, + "sha512": "" + } + } +} \ No newline at end of file diff --git a/lib/Loreline.dll b/lib/Loreline.dll new file mode 100644 index 0000000000000000000000000000000000000000..21fa97a162dc50da3cba335084a827a905843abf GIT binary patch literal 654848 zcmc$H37i~7^?z@7PtUoV?aX8*_e>Ht3^TK{+1&t>bdpU1gqv`aO@MGiATThSMPXb{ z`FU}8APD|`Dkv)8i3ch_54BSz~kqE8vft!tLmQa-Ax41Kl@3ht6sf& zujd7s}JA&YQr)N!^Zz-o-vG%;K{$W^1J(=E0Ek%{8*3i!Q=yrKGJpA1B;G5 zXWLlm!dCO_)~V;0PCxa63z|Dhr=3}9jbBjOc0pvSYb*;sd0^v}o>`df?Us<+PE-UK4cYM{&_gTGC|TOZ0M z!J{Zw{IP7N5i=e%Y_P{e^_Sj=f5$HCZHnWsn=t0&-j2X%^8H<@#8@ge~ z6G_vxoi<5cI{?ORESDR5vdeJBevh9C6vVC=2Fvv#siR*m%`~N%rhTU19_w)JW~{^2 zMy$XxWW!}};t4Z}luj9Jlwxur_A%QmjMe-z%2hd8#hQOXBrgRoUupt=h{h_n5oMEfl$*+dLAW8`*5;yQ90BMu_Tw-GsB*p!Yd%0usQD&)5f{GgUTzh^YR zBLaTMb@(xnEZ$103C-edh?HkQq7z9Cxr50mVagm~rgyTM#ys#n3eOn$TK#6OGImeK za8gziDxXSM7tib}rx40GGtF`u0TWO3jnvpe=vDJWzy(G(0R4|V)-$tfXC8fTUb6>) z?+X1c*bLK{X$-7005pt!go$a5m5?7TLYC%^KcH+Ck@?xcfjYWl4Wy@>*hC)+4~^dg zk6hZW#qF`>NJ-~1PMb+JbPHz%AY;q;vk!hUrWH?=T^o9ztQ8XdIj7Cs{TU}Ywg-j7 zn1WR{n3f&uvCDIiuUtSP^a79^KoUDz(qm)jfKFeJO{qAQBUGlIn94Snur&&n15o1G zWif;Djk7(;!x0)O2Sgx8rzciJI3@(Ej?;up9nnzs#WFgp=p-YST}FR!DvM2P0>ZT` z3(c_wWVz6UY9qdKhOsk`_A{Gk`V_UC;POQdaNeVA2eLwuZPRMjKxzU?hH(4NE}$(0 zsR`t^*^`LW><5Y!>Pe_tc`>j+gyjL>@^=%Uy^ngDOjz$Rs? z*?1oeO1EPs%RqI?{rHI|hJLPWSCPul20)ffeI@lzb+Ol8c32)sK{7|q&M z4`YGEvoUK4vdW(u3xdh!hoJ)7@`D8+7B;yt5jKLz?ArN(PYF0FP$xe0pA}W!Le|N~ ztLM68iD1HX6H?r4(k0(ib++lI3Y+kwYEeZk06>bH$`38=6vf8~3gr4FTJ~`v;U>$H z*>U4TuNFPAjhCPn=x4qRR^~HW3ZvcCvaUZyM~p#Ibt$}=gdnI$iNdB#vM}16NDe(* z$wN;m|H5b*IUQApnCY1DPPxrEq{1>d5N{lqki)~U^Oms%<$%f5Z8(NOIVEj4qS`Q- zAMAFl@)`)Zi6J_*)Nj;UkKejXtHxRw1}_ZZp}jxuC?jyRISxJV%I(x0xkwe-Z0;bi zi7_{I?I1$s6v6u_u+_}5XP|5>BWg#3?W(ktBbn0GjFW55Vwq$lv4);c?nFgUx&o7b80O@-|<=j*|Te$)c=|ait=w=`KTA&%a~C?E0|g zv#L6(OTR{QT5p$r|5Eed;dN#!4b_v#*aM_zw$SVsN`4Ixv5A&dl$iW5g{Z5D!SZn_ z4RtPNph@I!Qaw>Fk=@Md0#m4N*A)3AwQ>?_jEEB3Pf78^x-K(xiL++&05-VU+#e4a z+yuY;1hMb2T74+KGX7R;I24AJ)^I4t&xm|{>tMjHfDyJ^V+=tQ(+r9m$;XFZL1PFWt2nu5Rx5cmiXV1$TpksvSv1U>=;SRx{z#;64&K;R=lK#wNExF9eB1U?kPO0>?{ zi;$+G(%uVTzfcG_H&(=4C1tig4sn*tXw0=Hjq{yI!Py2iePakATrJITaY z+fPc$yrADSE6K~|sZvZxDHuU1_-OtD8O^Mn&&C?JK+BSDOePUo$Bkhuin&hfesC|n z?>G3gtN{!!}XaY}}3ar!l1E$`t^sDFj_ud4!SWYs~WQum0t*LU}eI<@x+ssM02r z%O-Ld=92vx*LD+{8VI`y3{@m2`7nCMRCPu+Kl98pE|**)tIy$y_HaXDNLh|KcDXME zClG>j8Rm3s3iNNZ6(bM=A4&-6e5TrR5>kB(_ZZe%-)22d+mW^-RmVu)pNji6PsZ~Y z3R%)*>v7^gY8`H9{A?@-K|1|ujCsDG5Ogsz`ZY@@R0)T!iTh*B-%%&1keg36euf~% z(IvnxE>UAs9N`sT1svn2*_;DwK2S0do`O_MIzzu5O6RGHy{y` zOIOdrNKIy$gqX_WsY59px^!@Z4y_Adn+Jz9Y*_$XF}N^M+}h#LKO<4>??h|eYON&& zA9_t3Tn8~7W+@z1n~@9}IIm=38W?*G^cGhwzf5QBYLry^3daD`z@maUk@g4HH61MK zN!-}fWf(BUm|L8Lq?Al9(uGW1`j(nb`Tg%a z0j*5!-T^H!o4<|GX|T)W-!a*VxBd@#6}vbn{YCU;i=ip0e$tuX#9Q!~1?aKxlqOVe zt9G2V3tG*OVIf-ECvNW&;iEuM6<`TN9I8EWsA;$?v_h)rNu{zQU*Gx6GjTPs;@l_! z>0!>}j_rr>05iA7kI^`RLKQ=5x8ik3fg-3DUxn6^HMpo=cd~dvM_>9^C5t;k(f(wy z6^brQhROENNETOhQfmtxrJl_LP#`BMDlOsoZ0PzyQy}{!ii<rWU4F%> zyVc$>)sQxixKLb`r6ZI7Le;`DskWyTNbT4_8UqK@9pQ^2T%MUz3S18tWlHKU3wp_< zf})J{v-}L}W6Ta+)hhjF9@7^#dam3DHJ?=%zzhf@49H7KQ%sCy9kYk_b&3O_l(Jdyg%$!+B`P7TQ2wP( z#4r_f{^FV_x>Kx%042qJ&QO3}3Z;U2F)0IbU%r)vq;Bskw2Jba-RhU;oYsEwoZC7^ zp7UC#%X5C~e0eTtT`A9ntyl0V1A97;rRO3T46#gNB``8$eTh{W#%tKX6b0kj$`dg3 zh}n8QGOK3FB=T!$-?7HbXXA-%0_#jJsIu`aJex_88P}1%!B6|aO9$8a!F59cbn{@< zhc4?tR}9W_QxUTt%V(V`R+(;M=+!QqA5|nyp(=Hc zW;oo`$M|g%R)sZ@mg$EYg~pkFs8Oih`=Lf5tsw1!j2{z_4Aw^>oyQth>2#P}%?^!F zL&%-yY+;oin!A;0DJ~o8(V-Q~>_WOaJ6WuUv?HGAw+opna)*iCr^kKU_#DcD@!W-r zhC-@g;uf0w=`kx02Or(8-HmI3oEMh<9wto=FA=wIi? z#vehB_9qCI+AVQWGg0TFUdD5(RD&qkQCt{RPFh_@O+CH=r~V*@#Z){Y@*LSgqbNF2 zhMuAUQ0Di7cx?qVLPF_kpLVhB!2AmyZcJJd?ROI=lW5c!;^-d+*cA#es+UVw(!NXg2Z(_v*sDQQoc~>Btx{Rx2`H^Bif0C+VX)Hyw z>dQhbE!8X!q8rF6Do!V&>h2Ox$16+C+4=I_T)(rwS_T&ukh0rjU-m_`L_O7G@AwLo zeQ7nLd@(AfQxht&nzqDGBWWpToD<>|F9jtvwM@s`pG80DACDI%7|3Vh(*H$)x~rSD z5mP{IxWpVx>hpu)K40Dn@@QwF6wZWMD;)XG1nmW!lDFrk)Nu&9ie=xwppL(oT*t!N zZvxbU-MBE5?^cCFPiO?ao>q>1ep1y6;i_dF^kZqDOke~)ktm{tcbLXJO)FOt?W39%4d|vxCpV#K)0Py~G*+u7>hin0S!T z)1C3}X(*qCiEk2mxHJBkhVogM_%@;YI^&NMI^gW!6Y}^Cz%c6^ZRQToPK-YZa2p$P zHhjG05*AAn#Q3+%B)=<}Z`Sco@*AxMYs^FXx}1mVaLJ zKiMC$ut@>ek8Ro$@a(`N$TjTCAdAJ%R~A2^EY>?a_=K+g64kB^tEY_D`N6(;`&WSW zFMt(hve}lBv{{hy=`ddtgyt&iLuG{O>2##j6tf{&NafbkGAk-D6iBH9OSvLJI&jB}`4( zKA5_pFuEOQEc7%KeHzOM>Hub;(x(%~9`H*o4e|S@=A_bJz6VsVfYee?RW{wJUN(c2pz3~DRkk~*Y{su_ zkK2RF_5_uk;m(lC&XCFi4fHbqOl7;JvYAL_xoDM6H&RA*!Lw_Z#jpV>_^UWKIwooI#Mo0vBr(99NVqYNOrX zyg%A%-3tq?`1zaUGDlelx$|$n?;HL^s7gWkK9C6#oz-V=3eRn)nE)-O%xbl zKG2u=z!1zd%wohwn+94;{;cLg_-I-#( zK3}DPY77+XJ!%bN6dbK!3P~mau&54_0f$+VC=kw^D@>%Fmr#XG`V`K-Nj4%G;1;f=a+HAG2neBGF zfz}G2p7?>Nw3?BBK8JFYz;@s<=4bDT^D$+j~A3{2Fr!1Pi=u&e2HJKAfU@tEe3_E z<~l(%ht^KjYG#{8c;vtTh8>DoYk!DBH(t&PwQ0-b^sU*Zxs%}1IiW45GvKVIoy1u@ zF_&WSiY3_u_pJ>Rl*y9W{R7n)~Si2gD&dPnsNVNpyNb5C)Ev8(Fp!Z}F#rE(ez8puD zsOMffQMaE@CCKcPi?na!MyN#sr*>kA;-!N)d9#k8 z&4c*aI(+;;EKw7lFK05=?m1TbASYPX;vTBJ)mdh1oo}nFi9o1geY74Qh+`vW>uMhh zM`ZPR02!1yW?IHN$Q&a(_TV~3MjANf)DnStPsdr;IO;k#&br1?*WP$>YdvkY?)K@M z&0A2d#t)^(OrnO_WkQ`1fz^W}v0U%Lo^2hcOtyHpB16S2`mI#lOLY>NI23jBG+qTH zScF%^69zX}3Uwlti$DxjC_R5K9n9f(orE_J!uem^y0fd=Rlv>xj0gwG zhFPp@g^=1*^Ja022I$ihdPbwvQ|v`gs2^d9Y+#DyRwG5*okZsZ5}iSbrsCM%o)<5! zSeD266s5|cMjd)*WxWNAq*Yw8)5Oq$Hym0PTgO86dM&n&h1PK_)(xBQ?+Fr%LE02e!YMQrJPG$ZHPO8#Jp`SFiv$H&DG) z8vT40rn(t3m`D*g4Z_a5KaN2=mg;oBdXpzwRD@tE)}5R+jP)_a44(w8FV1p^A($T6a>gGEn`R&OT7-ED5IS^5oS3bvalKs6)(HlMAbkI=`WDCt!KH(*xkZ z9$}ehCpjXE+dwxMQTsHi-=R2FPr5UL%AU2es~g4(25o&l+l#7cTcOP|0Lq^4askj= zwF+6TBIIp9zt27KcXha#wsj{euw42VeI}v)l}}%`7(FE;BaA*DCEEycJ_-kv7Xg38 zhmc*FW!)1Y2~qi^{KBU!LrZN>9_9Z>3q9B?91yg#)E!rM*n#CZ)W58v1f`Ew!B9oD zB3SSws)>TXUW8|RYi)K?99VouGDX|cyq!h{((q4QEk-|L=iym`i%^-pMHcL|Dd6#x z99|>a(^6-;WEvFl6<{=L-T{)B@BdWcGmT-{k3rd!D0^hhlftW+BMPEk z%M{I;UEga8Y)*aF6xduT17<#?f5No%tLvuZnllacxzkYR^bEp95eM!=1N>Wf6ejbq zjI&`c_Mx1t=G|yFxCUiFuy!{Kh2uug>*Jh-vUxKWjByuq8=f>7zK;vjnRc&*(`>GY zmg1MwY_5ss;&*F3hhH88OXz89)y{L`9Sq-bVOD(&Ps(X_dMH-Kp4uO%k~lw!$pWpd z&i|==_w?B(kMt{L`T)yQfaO7LRn@Y#l^B&#lY=7PyYb(Y!Fx9etSNZht2G0IpFTOt z{J3J@>#ZL>)*QAA@mfKnLtv zZ`6%Z?eQ1;zIzDP7zbX>Hl6ZTadF))ma1KrcK*;=?uX&$#dK(r>!;0uKkw7 z*1>|)u5H7Df`L%6=1X~H{@0I{`W#N$=-ZuK8kVBCSBQHMnU*~m$!36*zT?$T={*Nh zGn?-KpMJdd_)b3I5ymXSSrKD#F+_9m+`J8tv?E7(WguPgL=5Y^{F)l)ba<0Nq0Lbn zvk(5oqB9#~)*OE~=Rj14yE#Y6b9UKN8H>8bdQSLj%H>vF>@lq;K6p4xy^--?eoyW0ptWEu*o-1#s-evozxjlD}XnVKi z`=CXYS=CRK33*4?%ntw|aKu4chYe-Dc9A}JEsiVVbu+5(N^UwsQ?%#cU#O9Z*Mv2) ze`s>Yq$MbNyOTh);S1f3>sZ!~W!ja)dRwx#9LMD&UQM-!^{@9@(`Bx_)@y8O7uhlt z9i8I1R+_bj?&wg<NRRD9*7B>3-W)9sNqT{%l{qR|Yfp|7vUh6>{+8hl zQ-sxg8wH_x6hebT<3S2e$j=LdDNn0-(lD;02v~Cts*q%Eu@bI&%z6|x1-Y8UTKoA0 zs$Ohit7adRhMk}ZU8NUQ2BYbO&wYa%IEL)f|Lp(v!|Ivp5bxdKXsvGGpx&uaIS6sNrC^j-a2iH#dB4Z9?TY`hrFQ9{v(vWsbtzicj4Lx9PjaeMZv}#lmrIlQLu(^j2FjHdl3Kyx%w0l7aME!9Rj76 zEjt+E`u1H3N>Ez+t(c`Lkas46jlDp`E=lX#5{zAbRIhiaMJNe)Un7o@wmI|cRWy?XZa-YY?-dZ&`MBKW@h{%6?xtFZmd(%H+sk4uoW zyu*lfjL@d4k)~+pM}&}Q=Uzdohl~A;1}%KMwJApvh6 z1RHOaB&s;e+q`}2@!p73X~qTDZS@|IpmcyyAU4JYd*>Vfewv44X%zh@UVDbe?HAoQ z;B_z>8wG3AFS=Wl?|=O9y6vR>mQ`1u?KOm8s@FNkJ5q8n)%$Oj1Peb@7KCEi1#mMi}2sh8AoPLI9STZz=h9r!U@ZCGL<yEBS!e6Tq(E13o|i+tLR- zLIBmt2mFNqwv!J?#sOf5_5u9_P=9^E`2?^%eZU+1T{RFV%`hZ6XVE6R_-3bC< zhkU>Y0aR`uu!R8Xng+-i4j&LrHXa-UW#C4b(=vT6l1%&jFs-zYLmLfL8fQP5rr8g3 z`1Da}mi;h|GP3ol#~$jjq=O*WHu z7;K!4H5#9kxESp33j;CO+YxNMO_JDMX+Fhze^so9nBsBfzEV)c9xsz5v8=omqj8ob zv6HQSsfd8;#Nvou20^j3Pe?AYv@Qu^j|Vjo3)__9h=u(n3C@xvngSL_%tyA?=UUds@G}iIP1e`n~r4vGn$oT%Ws?pc54Tw&PR!ZMoxe=t|CP_j#j-? zoyD!T%-Hq)QuO?-a?+@JO2D&;Q)l6-i>P=~x+9JqI8S5x)$_&Cd@XP~(rUg&qA)<} z_Bmc-DtlA^jVJ%C)_E}8YGG`(UK9%75DLE|6n-QW{&^^zPIl-l3WbM5;R8cq*yw=o zaUuA5q3|W4FirtXI{5FuZV+4!`w>=__f1#^CC@+H`yodnutjQ>VV<2@)>cosH}yW| z!2Z|xA26k_EwI%yYg%$mur0r3$VwsoRwK!iNDSEWTgGm>AeFaa9Z4qg6eb^2N%~Af z%(xomT7gwG?!d6h=K(!k-CJLRAu2AHV3>BId;v+#Hccy0J{LiP_$*_ct~PV^-4maN zch43|*o!au@5Ku(bsL6hVLyx1xCI=%|Ng(SpJf~CP@a|C&yssaQ?@*Cg(!ChaWzVJ zb&L1SgL8`X9QGZI=H8h7&j=VOM@(wh5|L<#GkH zO*DB+qje2Bo`e)e^E*^Kd4EG;p^wFkBOxpP*`*s#I?kw<)C#qpvP)ms+nrlqSmo+i zR`KK}Yh{`&DMn|lOs0sbIg+f7IY&BKkp^w9dg*f!9`LL%USCq(M|0WJF1_o)3mQ1g z)w%(NmWV$El}f4_U@}HVnvXvNJWFSeo6Y+vE0!X-rZa39ZwncsJA<(Q2GT;E-EV@< z`%hmZ)irpz2Yd9m@^u2oG5Bs<%-}WH{Igmg1vyiGOJDu^O{SDx-Y~OTpOYm1mSV+^ zuL~!AO_KOqI^xe~o*zznRFe2xYCUzZ5l;HCB=NWOTKAYp(vyn-9fF$v^^sd9_o)S(vSd#c#I`ZkCzbKq^swDBZ^pn4Ta(+0eElK<> zedCx7k;1=5lK5NtmN`t>8@9}r5RkTi8AW8f!-F@UoBT0jj#NSf6*f%%K>u^cp zZ)yJKk1q=+oheEDEuA~7C(>S*OA>!e?@GK^IVN;5Zk8nemUaw%AX1FGC5gW!`^j_8 z3iG;OlK5L%b^qFkpuQ(b{4KruPvenR{=FpexAd}iouj-TzM#5AQ1~rn|7X#GVP1Wb z#NX1E%OBYiPVyv)zol(weE6HBP7dcfU6S})`cr20ig41HB=NU2e8j3q z($$j0-_ppo10qSUlO+C@zHq_FhA_c9C5gYK7k~2?o5D%=NfLicFKJoI^`oWnh$Qj1 zbl*2OhE!C|g)R9e#cGWCW}8;JKVzy(f|bn{Sl_O%*GkFlTFw&x!mZHdT9u`izFtd0 zd*CF%w#q`QT;bJFi;;#YaH>+Vw%gmwa9sF7I1f^lL+tIb?LZ-XJHLA1r(zD9s+@)! zvzqYxJJ{g2Mz?R?GvD7M8*~iiZtkKPzDnr&7ob}Gw z%Q;-h`rs}#JQ>zFhRv@2UTd7ou>OQKj#DpPwQfsYge`P2eU?NZF%d}R3}!C1f_h_d zJ7mMZr$#pW=$LhM)iB@WB(S^=;_wsTChDp!wPdMVn-OGG$vSEn(?)N%`W?9q5ycVu zaYet2us(!CkWkUUgC2%>R^s~-TS>>ooey0gAr!ln|mP}{V{UaynyRd^aHd->4OPfXIak@JR0&HOmL90{ ziHanJiq}Q-jpf^7s=?Gv({i>ADrn2`6bUS1MzuDC&hZ@u*Z*2T)s1{2fUj!C?V%_2 z_5Ui*S;)ga6x1lS3#G_SQHoTxvtAd6uTGyo*Ef8?Mke&L9gY|Y>*MxU8u5r zhawkFgQJmO?l?qaC#<|(hCkAYAff%$=RKhJ7SHt=Bb@)MMPk2VkjJ+x7OHPoU@ZnQ zIeJ-{L=-yU;I`%X&|`UZJ`gOzG@#BbBxw|;qhThP`SDq@ZhfwKh4JJ0%G-5;i`Hf6 zgS?SDgAUzy1tYkTn~yT4!;Xblid5CmrkITausINqU4{X4DzX~Tx&Z0Od)gYOPp=WN zn!>kq!~{8ktx4mQFlED!rSRUMBanAyN0$4myf`CU-CJiZYzk#opSH+YjwT}?wBYA; z;0wkZA3R#S>Cf&a2g0S~_Rto-nBIGOV^Kk{%HWBM=E6a!ENq$Rzn~ zSt5s`Tx{cN3Y{7&Z$~_b53hvL@u3VqdOp!b2OQ%#)O{`Lt`lW(o>GxquG|79FFUR3 ztI@>}%KhjHC62elrASG&;3s4MPV=woyG>2w5b(g-UGr#TZ&e+=p@at%0RgJB(mRgE z+6z}2-3YWAE;Qn^eIKsg@sBM(fMyG9|N33fy>$0flZ%q2UK3RR0=6)~AF1{H{miCoj*rnTtouyoQ ze(a)$kFEDs=VR&lXD&85q|4HClrai5y4Zvax!yb`jsX5_nUJpL5hPpYaI1eHP)>>I zp69ZO?s=}Pytg>S;&RCwlOR*QOAu_pt%0QK505wKMdsSbs|O(OZle6J#JT44UMWHH z@ScFvzHy(#xx64Rt|hz;STMpJja7h`kRVgNbqF?iND@wb%;(9AlU#2Os3?&44iY?9 zP&jM$N|MCssqD=*HcAqgXsjQ_NS>TUdq1&Ag^Mk(UwAUr>qUmf9LdF5GkJ2#<&Bxd zCdhjQGB*AwS-8OSTnUngS73=Bl{ja9c_)Vl*X*Gg{r0Tz|2uT+5O@XPMr&q`^5`_VyR{O!clM z?Ya=4UXeZ5mtK#sC&)`8wUHJSE)+cZZkDX^ysOCmDM97Jj=VT+^|lBvPQSe`NRX-C ze#HK?U~@r7UYu)tR|yJ1-cCy6&4R+!fNc2MGFkV|2Ug>yDmNA-tRE-4J?_d>TDM@TNN-pG^Q1n*lYi!BQ}k6R6ms-SQ^=Bd39E_}Qz$+K6IxPB%t zt~b36B0w(Ry|@r!s`n8R`jN!Bh$JtrcD#fXham4eB&hbAxMI{LJ!4?T<=!hP9qu=o zt>{c1*EvUL{QyK)2mnM^697b49sopFDF8%QGyp_bKLA8mN&rMxRRAQp<_K0`07O@7 z07O@I07O@T07O@e07O@p07O@!07O@<07O@~07O^A07O^L07O^W07O^h07O^s07O^% z07Sh407N|q07ShF07N|#07ShQ07N|=07Shb07N}007Shm07N}B07Shx0NhM(OyKze zAnGLoAnH*9AnJVrAnK_CAnMfuU>3Oq9xwo+-ZB89o;3iXUN``v9y$fd1Od^4N&!vn&HTCyyS|EqbG~`lyUSh5*`*58Asz%92rM@nR~V*MaI#S zB)5#C>O6pqqeC>lzmVLKar8K;gp8x7Qv_cS6d6Y^mY|HICX4YdiOV>unoPz~G+AUE zy+;~W#?hjbM8?toB5mw+q3SYzs&fr8Uh=@1t;Wln$mRgSij0@X3QZX=@0XyAmm(X^ zCg8&lkC)d7ij0>$<<$6tR8hvu+a!rq@U|h?__ic*ESoZ3ZWmN~>b#dpkfW&@FVzXW z$auN85F*Hnljju@m+|sm$t~mMX7c2rJzMi;KKw?DryspE5mGkW~6zk|8nET~TwV2=_mFuU^8&esTd zQ)BQo0!GX>QI^0C!N+mK+RfYOp-DAA3SZ>X@NJ=d2by+tKll>-Zy$v2wM|XA*ft&4 z?$WsSHpi8>`FKae9SwIBd`(=nkfR>UQ>skHQ=hCvQ}zi`Mk*Qy_Yi3=y^2de?zj@S z_0gJ3uj0~=tC0l0Qr&^2E=n2*D3TX-O~D5Pc!U!#(BgN$^oLaCzNJu{-_}j#BQ&2? zj_JAK`xE+RVdc-+j&|BV)CNg>ahfKlnpPJKOT=%DgrBDN3?wZbm#_a$Dxro=Uxuk$ z;WIf@+y(tw59KlC+jA2*C4wMMn>rJ4y5dbzyjOrv*3BX+%WlbcbPu2}N@Z9HtX$e` z^C`+M9)EQsYZ!V9?#c{Z?Ho>eE0Wa5+hu>qgjcRZZBO(JXKY{aFMDV%!GK>jZrlkO zZV;c?IYy-hCa#&f|9n5X@4ku;6VN1!gTPy>JyvyJ1w|SI&`0O z`$>A>ld-YZE5 zPnYx|Njh}8r28c4@ad91BS{;lOZt)|9W`Cj*CYuCOs5L!5lK33x}--X>4fQ$zAs6m z(g&k&PfXAJi%cEIDBZFe&XQF5z7o$8t5OF~h^&=C--ug=#eaILee=XA6eR#zmw>@wG z5Idw!{KF8G?tY0Cm!iB&}zxd(DGSsQIUxy_w-PfNML$BQx+W^UtP0fTAb5)On=CXG9DAR^SAk~$*h8ds(Hod{M{btimELv^R4-L$?G zPtGA^yY1Q{w>w;f?f?TfWz(Wp=Y=34Oi6URDkQqe$ROxb*&e&Lr`r?Cmc?m+ z^&Av@Jbbf5hSEh-;GtZC;`V@<>T<`rY1M}^($Qo9%FeKB3)~s8O}GHtPjzQV!^_Z8 zErh_aO`Qad1me^5yo~C3ibJnmTkiIPLoaZr4pA9&Ik#5`hjJ~g)^~%g3lhfHBr5ne zOT2YAYysB9@5kc>>B2YRexucBuU)K1ah?jQ%(c`!tVPH1JXHF*=w0fuZRMR9)SCv@ zuW()91f5wsGQNXPJ^MKTmcc;(<}nz4Ld&QQweDqIJ68;}9*V}z_F9%FH^E2hF=yx8 z$BZ0aYC-0%Ui{!Ml`_tD5%tg59ELKfZ@F9YIt0I|I_=94cxS;zZ}i6mfgd; zWS1q4EATuV^z9m)4m^HW?aJd)2HMbn^Moh0G(6`SvO?`4WlI=XI2x>bWX{Z&Qc}*! z0i;FXe7$7l9D%a)tF9iJPOBGr7AW}C)N^@mNP|C&{4ya{GxVCa8|@HWJ}-|pH)XEw zXZ+}-jJWoLWod`t%!s$3>Mzi=5`8FXVDRU;u-%Q7W?GGwnZyC}&>AuP#|q2f)EP@5 z<5l?o82%^GufB)>JkNqRyp22Y|7-YfV^1uH|9jy7hp|ieGyK0YVHoMOVVr~iFUS9% zWDMh^Zo{}6|F7>cj1S`fkMO_o41S#P*RTigfz<&%pTlJgmnG7bS(ZHh?}`77hk%aUjB3H!hu;GW`VodtwvAjk-HR)A)p|$uDt_}q z#(Ly_9VzVzN=tQkDZ;pNStXPiN#yu(xDMsGqMY$R>{GmeU-e=4?0nTHPQ#v=`dJ@R zV!y?RD_=2ioh6eOpx#XG{p6n4kvby<9T{L`cF0BI z=0(!-U8BDK7rFTtWIPL|&~Bj`;=dg!y%$kHiFZ)@lo4Ipd5b-+Ww@`%m@Q$6GuOrJDSs+gA?81DuR9)mQ za7#)t7r7-UX31S>x{E{@x+9g`1xh_1cIQuBHfM`&QS8pVweNwUaf>0nUF^q1ahI6x zVx{tXxO+h9mXrry0OM}Dd$>#7h0VG&C%(BdU-hMhO^lRyA$R;%3TNQ zWDO&A9T{O{06S(he9Z-5fK9RNpt6|Rp?r3E^-#CpU9Qw+Z+AJ=Wx2a|D~X;|8vajz z5+$>v{zXV|nQ+nN9~o4mLw~nBl)r(yhJA+=HXZ9Ocl(uM-36)x?vOj^*4<_Cu{Jo% zjz)Ia9a!;2XqDOKytjW&Xd;6Q!(x*g@1WWZWcSHs_YK?Z0X3Mc*xg{V4+oPK(^!8{ zlEGwO)d!{1TVx$&z0!17a(G%PdV=9;VlAq9 z8P1W0+cdnq^uo)_Ri^7n&g^Q_U8O>6+|_D%Im2D;u92mZI_3_ztPOG1G^bjU8mtW~ z?rOD|LoT(LTO*4(z^7SB%fXDj!1Ln2e-N-c~O#S%*8r)otTUH z!^_=uYT)8nwbYMrtO_C=(pn3AL!Dh)O@Ixr%w6lQUGY`StJF6#<*RVn_2I~V`Rx7; zOpOe8#NA&h>;dlnP}u$51Ewj}26uxh)!Ij?O&dBRYKMJooe!76^*}!+qw7KLdec2n z+CZDl{Y>|uP`tsb?@adq@w&8@uuelSi1~Y=OW^LW^0@9x*EqQ_U%sB~~X zdk99Q5%&<)YKOY871=}FgH89)kT~5#G?k%;-GiZt*eEsInD*M)!*pZ=BZn)ZN93~` zLG*BUqau2wyAeb;x<{Dqkzt}6-NW@zXS#>EM>KH@42Oibk@$h6ca-TKB^g3C*BQTq z>4zX4Hdew15f@wY5;>TWV|3(DMve_n5pj9>8g$6)@wfw$0nywT_?bZjyz(j~XhRyD2+5nNlvhIc%qnQHFBM?hNHIG?ZJWv3Q^) zhH|sg`Uu_Wtmf!c8fww)9vi7OYRj)_IOA`pY!9Q_lCRpa07o`gn!2N@qu#;54W z5saLwBRD2$XHPTTQ-nfj-pF~tsRG1o@iY~1#&M3_Y#+%yr|SrAhqkk4=*ZEGoN2nJ z3x4*jeD>^o_MFC*G)8B*=cpQPbI(By&&g-cb+@%HVh-Mdj$PTuv)o>$$|JKE31^T(ByIqL7XQ|6YVukG4)wFxAd!DN8|F~z@=9{2+IVtY__XYbYin=8DD9btS z8G<@;5iYWI&+d2KGdXIVDHvQ-o#mbdf!)a}9>a>9uOr7Ya)Ifd?_SVcg093#&RxVh z4hiTh5PpD{L(^;9N*JgfAU(-CC z<#`!Iw-M3{b+we4E@a|rKW6D;M|65pcT)AEeUs9SxPn$oXpMw0;Hledz|~M>Lh%-2;U;*$y+aq( ztgP|g>!|)eXr#Vl9IfQtWgHK>M?v0&6a>oyd5<03DWLD99#T%~OhU@moeWBrLvHDi zGxMoxPhwKJ=oeN#Xwyihjf2S(Z#Z*w#p0Kpb3X?DvoWWm^$}269K%*+SSib{_#wnT#}zp z^V?#va)amVy7HN%#8#+Sw`*I^IDBavD8Y~IQiK%@Y76tUeriR81hvvb^8?i|~lt%$sxMDlLICo-$j zdL!(PC5IH{qMsLJD$RX?gmE*pkbhSH#r6y0(6H3HPVeR6G>mxY@f?Cu@!jTx@kUj| z7rczUYI4TA0661{RTvsZF=H%e%osyC`_SidcprZlT?Zd;mBp%E#(R-Ys#?n{N`<>bl>&^v0~805K`y#t$MT`9a-e`$~>W?-K~{%}k01;cgTuzlNMuU}Kg zJ=E53_c{T0Of5ClAy!!g@PEBd_O`raR% zX6k#Yh2_@9r+{gfj=w6grry&0k7?X3KzS>47H-bm^xyl}57ciT7aP}0qLlCmyY$W- zpB$~9W|zJ)nm@mOSak^eeyw7%S0slz znWkbKTRX9pDh9p4vESf+tp~Ad!auvzee|}2S1-pftwgZ;27!L=yk9xIi}Ut3o*-{9 z-*m+hhIc&-xB_`C1RGa^h!$8=g3RT;7)z?gSrY%{w{D!ncd3?r=nFj_-`=!K1gZO& zabrB<2hP}D@?I%~U$v;P$ip@Opp?A#0}@&wSl?KF)H+_(d{@`aD@ky`d1laq^BSlG zdDpQxzmquI$@`cD*|gqm5+ng{Cb7OM@$dcSGx9d^n`VD(op-g=o*?gR1RHk>3g_+K z0a7wHueVj86qolTv96Z5dsTh&>RUx-q6{b`k5RIa=IO6G)cc_jAjtb5*<<;GLhqgG zKFm8slAe73ua6M6N_)H?6APO>!1~+wTAN6SisY#)n+ftJ$lex|KYe7&sE0F^z^0mc zKM4>*(nHoXtnLI4}X2aFRy;rW195J2Vh0l{L>2i!&s3fTv| zp8yKW2TZwA`+j1uF?@`#6Tpt+10E-U{m=*el>q9S4@l1hfE~yO6bWFz^Z_dfV4w8? z(bK_r{qs;_PzijDQ39y&K42RG)I%T8CV<+e0Wz>)_j(=F0*|%oL7Rsdy*7HQNc_}l z*GFx)A(<8q51O_~FPqY~<7}$@O&T)_dd!$#U97mO9Vg8@h|n!Q_0DF#@y|citM#4D zlsn%X6k~~(J;g}!`$XDEc1uu{TF%wvGK)Y+gxF#N5$A)R-$MV(1=_ zBr#;aV$w6hh`o#TW*_2PjAGs1BS~V|4w0Z3wiC&duQJ-w1<6xP7`Dwd$isTgXuMre z#BNK+f#aInCQh_+mkMh4nFNF$z>2PTb34q+6=ZC@1cmf@?d4M{{bdgF}lCpuedR_i=GXH1y`9I-)?#GStaw`~WS-*p3fk zx`;cP3+j$$RI?c5lQ;CZH{ZC&$Hq}wQ?|*Z6zcR9$u#HrxhN@~72@l8LO^XIlQ9KI ziE)OCWL^l3Lph;B`iXKl9;cd+37}?SGLoE!#Pvp4**euZW-})U@^Vu2y*f3o-GS2c z&uY!U=D6}kSgl)N@BHwcq452o@I#^S_e0?)L*c)K!ttz6bGjGz_JjJWvsE!Ut9C5r z+MU<Q>h#k^5o zgoJr*iSWXhsdeYM%aD1aya);NIw`^n?+XOH4t-Gq%L&%D{ z_gwifV}uU@AwH)>`GEXC$A=jsd|cBjOSvLnAPXQaEMQEf-1}k{$8>YUdQ1H6?)e8k9i~VBOoNdOk$@! zEmJsh{rWvup3EBINkEjRIW12(k9?lU@|^0)tP!3BOyW7U5ix>-A=~h|@?*{jKLVos z^a3Z`l8~hEGmX=qqs}m=;uqd4!;xbqD88WtRZ|6^y$m`Bwhw4uo;!bLoWj2b{!G{f zfAto2g#QVO|8uuF%ove30U>#h&7)H>T-8lvC2jj$?ErHsUS^wN&1N+?D8b?dd?iuR zH;o$Fl0MjHbq9i@<~Nhy;U^8OOamGzIP+wBQZ-}S{df110nFO&j8>jMjm`f88e9Ga zG)|gAqY8N>L-OFA1|0t(j%~g;@K%8WS=%@-2#Z77<+;+pnE$+KU_75h!!qj6u`X(? z?vSB<`X^{$41V4;Ft%RmD?4W5XP`IWI}>N}5t}#*4>c~ujJ2raa~T7LL|f>!NeACY zk+s~@p^Hk+R@}}}+_v%2V^5qb&)CFy@^mJ)%QHT4KAsi4gOD&jYT=Cm=+5reM!caz z)~noGfOm^a1(l8jW ze8eU$-~%S6DbJXqke?8Gin>*sZME(P%jP1yFi}h6LQkK+dP6VNYtjPASmh%zZgrbU zJDzA_C(1G738#4_i;r^fZsJSu#B|2ZkKsZ@?AaD9an^{9Nt_%=XSP{>nYaY7nDXRkA*phti~-@vn|%Nctm%A<&I4rCj@Ltbop;+t1WvC@t20D`F#Pbuv946Vc~a7pxKhv8@K zeUenzG$~P^S1Ha|%l)>*_$|zJ88R%@;VVL6w7=cgT|O4F?Vsacv%r%?$=l^@bNMJr zJy||l9&*LL*`}UI%k_7o_cJDTx4XyxI@KOH%1@!9kMjYznnX)Kis|>HoWiD*6EBR8 zEI^;CTxyrT`J0>U;S@KcnF(97YBN@U;#qW#yJP&bOU8@F`j)L#nU-N|`L@jAM*Vyh zua7l;DM*_OPq^N_61-!dqh{8(seJNgdE-k0zv`*;*E4_rS3W#gAG1r0*EcraC5gum z6*tt6v`bHX^b=mcHG?hj+ndxnnp3vEH zrj|)QI%=h7Nla+6M+qifkkYXvCREt3Ix(skVxf&|6pj4>O)h_`Zrq5VIJgl(S$PJM zmAJgW0Z*lR@R%Eom6G(yZJ*rh%G{M&W)6P0|uw-WDR$w$$0qk^LLUK@Zs>Tcux5*MkzS%TcNs5cr=Y3z$W z1e+oB{k^l_5EBJZSvX(#34EfdHb( z!-GwN2is!OP8*Iwh+bp*LMG8IGqh9O_zfzGJ zyh3&pe5XFu#otVUXJ1AzF_(>30zZP9oCY;ES7)K2tZpWLu*1Sl2a=Y}2O8e$$_PE5 zwd^Z^wLC$O&n9fnq|5eDchg`c_-C~S(Nz9Ez=}}#gi!drPZz%kwQ23jn z@Na^!zd!Ju(0uvD5H$v~OZbRQT*?Oqv$i~A6EBgcGckcD=gBeSL+A>dVY7pIvRDOw z4?!X+vrYUyWfVunzBw!Y4;6RDK4anDqUIOyn{MvJZzfS2P>1O9gG*IDQLiN;DEuVU znYA6)pgo)Sax1}XODWqdn}5k0`I!QdfirHlr9y4iPK}>!R;kSqkPZli-@#EdWq^Hb zOK0+esShy=GMXpH7orR`>=P3fEoE{C(^UDYnPg}_^BAUq9U5k+7?{%wA$FAh({)4! z<>OUE6-p1!g38oDGCD`lfkO!tkRGjthnm;$Qx1BLw72p0M~#gxqOZN|G9cHwDupW; zDE3})d56i*My|L5vC5Q*6n0PL%v;R39W=La{LfFIi8!HpDId9stJUKw^|+FcHh-ZV zgki2rjEq_Ay>1ZYm=FvUHr1-ey(05UJ&f_3QRo-a}V};71!%`G0R1&K&`h1J>N(+KMZBt*d z_l#3Z=Y9FfYc}^4yAj;`J@{+SgiWsZv{~y2Oof zeHY8dUE7V{TFc@Fp7@PP{Fk_>E{~TFee)lxiz@C`^evXoxwRj|GfVAQZ&KxUYy>1N zb~~o4lneE66*TElo}kyUVRSGJNjE;s=6M!N4xp zVTXyeDtLt37r}@8Fy8#L{P5w}fFIh9D$t zZ#GEf;(_ExMe^Yi9Of5O>zQ+-)->nVtexY<5JJN3w4S12CScq|Q$o~yVfNn@sb=GF zEIk&QV|QS|2$Mece*OHodWuq1zKjXi+T#!6S99)v%Q1)5Gu0f|DC|U>g>RFR2QzTB zTA{7_4^*0eDm%Z;dgZM8n)+HzZhrL?O|XQou+RPJ=K6ZO^bDW1>IN(==#veYs2G)# zEO*3k(~fj(iJ|w;6SL5XQPYDW$!S)#CYW#fWqRum7LTk3K@{)W{PH5t4DoFmPR0K) z+D>?Pbuoz`k8}OTw(zR#}nejiBB!(CZM<5)qx?xHORc|VsV=Bkf2 zRtcOkWL7qk{#*({s`zsRh2qrxI}X z0LaA$>`4Ig`heg)Bp)zB3{v$08wsFPe85%$C<`C3jQ~o-2aFLw5%_=!0$6b$@CpLh z1U}%+1hC~aK#aVG|5O_#Qw3wMKc!J(p3E9vNZG3#^;e}mT@V9e_ePPnu()NTCI0O7yPx_TE$Z6t}8Ac|C`)u?tV~E7AZq#M8PG5IlQS9Uq-k&4|Om^aYHf`|x<+SMZb;j~?{9$7&Vw9o^eI6y7fg`#QGR z%$IMch8&>-+K2UC!iIE2RBkb{{Drn=DWGFmZ|l%~3?Xssen0R91}+A@_aOksG$tNg z%ft7s1QPAPl^l9M5FbL6*tIWj09IXl8X1Y{{3DN6t>RPK@T{Nutr&B9!4 z7JL!FQ%MnA$46`eGkLpmPlpOqMFdP25fDQZE|A07l*5M$&n}Y^2JI2n6vinbS!@i+ zf^3~Oe}fvDZ5F|i-w-e;j?)f;L2{w;`4&s}EQ=P^y)FbL{kw#mkk-vC&sT;C$YU2@Y^nd4XG zHK2j2z=0a63c*qpCInS^vs5K;_nYk~T1cN+m(xOZIVw~aX2OOheuHA0ZSryCHvqCO z+rB@ z8JG>3MKcQF+{j04;`Ml7ph|F{B7RSd;h2!6AyC zIeFJoKBdC4516i&)u(F}ETML09zuC>MINjbKE65+OZ%Fcs)DeqpOwc!SUr?WK{r!^p%Z5}X==YX-VNmkxby@?{PF-)lHc0$>y^39$ zNDZ-Wk{TXV9o02ltZO(>JrJL5Qh47%N;K3G|Zt!jC zW`k3oDA6(WKU6`7X9V8y=7UsVN}~LP3>Biw@DKez>fQuSuA_e?TFpeHvAAp{6}SQ8N0g|IdB%m~^{P}!acD1r+?L82fosDJ@k1QZYu zfdB%s?}{i2A}SAo@Bcq_>-O!Q2}XJE``(vddT!OJ?bNBNQ|Fx8;~K3jQ^I%+YoH zSfdx?)AMsxZ5}V}yx=05DprCmlSK(Zokc4j@vi!azWAUI7=k{1s%NrJ z@P&G&te1_C=iYUA_T_L^Rq&kV zxwKJX-k>I9`#N2XV2wwhP7BfKvw^#QOzj_xDd!Ue14!l zSMie_{l4;jPkp|NkC~5wuyk2)S)&Y!VhZ!uk(oeGTFgUFTFh2YS}dvOY^!sxM-Fco zXw)havka)BMy1jn!1&~!)!FgZw%c-CG9k}yACNG&R2$EHHB+Cv1-l z)vA-iQ`1sv`I9R2`l|CfI?d4^@nel%!%uYdT7JMtV8=n=&;LcPC4Bl$8_Eh-$?1FyAP6Gw*!Rm*1aRow3p@}UGAgt zjSr0tC#clJ@Wt_sFO3Z+s5FD&)8ZR9)`sKdf?uM#D?uo=bT>PY-=Aj=g6`7VzE^=(^>O>CfMD+VxkC+0l)lhWG$J7_=1*;Qw zw(l`y^a1%`b$S!|I58!~>x>>%`TP_3907cA7e{_qqry%jY(-sxB!tE7;=e+y@=^>K zQ*8m=6;5#(6?Uo$?AO-N3uxrf%Cibm?U>8!MjT=5<)^A9^X2sNVa%?bvU)0>$T^D0 zgkw?4!jpEPvwS3S{2<)09EjF5O0)16 z>vFLO?^yU4=#Z6VH6@{GcRy^A@_<<)B)Q?59juCw*x2jYg&TTssSvk~vF)&;7UjxX z46jMxDt?rqCVsA_Nt5?KfJHElhmwWBlu?S}(~!ZkUde_l?MxOlW^Mcv?m_FG0m@AF z?I^q#+kxr8#h*#>>RW4VzkA~p+;mM;XJwIq|6X5mnV`)LljKtQBSQG6#Nlo;EHTjgZuHM%|)PcXV+anI2z9n@gWa&9@Flg zkAW(&vh&i2b~~dT{>XjXs8gF=c(+65n_q+GM}p>N`EkZz75t}!T6>q4e-GH$5Ru|! zo5#QIe{7s=>22N!Qz2ul;C4PstiRyI!h(H<>@#uIBxaeKfarSu4SDUTKIEu!$0WeN zyXyXZd^py3VD>7{C-|j1UTOBhQvsGsoe~J7G$^9D3KR-quF@1yP`i{tn!*;wCK8m| z5Io8m21j_Alr#@zICw5vyGE^Gb7zy|$4=!|VTiG_HpE(2C5`wv__U(Hv^CM17=5pW z9m0*C!_RwJ{w>W#hb%z2V=1mBA*zGN{0pfr#h9`3~_ki%w;;L8a!%m#M>0=Q`?W-CLn@L)57!4^6C1LAX5vsIF047U21e`%AN3#>>N z($CstdMOI@@NZF)`6Cv=Uew4jNTYd^jRCKBTZ>8`Fac9(%ZddgOwqFQQ|&T!SW)2( z1uNloXn$auLLWw=(kpfgMEVklQF20&sKhf;FisGOFkxouX&zHl{QRmA!%1dS3fhpH z9uFC7(w5V@Dg{<)2_~u|IdR2h9<|HV#l^T13-K6J3<^YoD!f=)fCP;UHJHi_I?WcH zUko~kM3_~Y5Y&hKKN@)ubUhYyHLJR!93)Rzb~lb^;4PH}SYW|L#PdM#O-v6-%Z_!= zLu6Cb&-_P1wNGUm6cp3g3>RB>IsO!{jpFb@1D`vZI*?}*_^BP%>}1$WKeAFJ{- zH+&4=tWX0V{1iHMxVQw)6{sK_DVU9$=8~bR78?LA z!K=Qc!}P$S0Hb6rqY`l8S;^8n zQ~`1ajJY*aM1h7_TrPMVCZCR{@%$nmD8W@K@_b2|D`T>3sHV)Yc#^QO*U6SaJO1sK zg>%UttjL?dxwIKL2XY43T~0Wg6O+9&vg>LghGRteopt%=#ey7@4MNU^4Kdj!;9Qut zbPR0P0&5(bOsJ@F@&0Kf{=JJGnhAw#*Gp*DyT8J0#pJ^7@~A!G@@KMN(Kzk+b%)EvP(%2RvHI=HLBR+ z-9;)a<_Js^#^9Ntg(>0+ICo5*+X8Ff2S@jk++y?Q>Bb8GzE*BrM~{f%;F~<|lK|T= zaH*(z3@UAi`)FLN2v!TLf?a4vRK96fMPU=eCtI zswD0u1=ml-T->sVC+~(4F;oC%2vx^>il<4IRQuD7Zlh052hZ0DJ+tX1Q^s<-Ikytm zkv4^jaNQ;p(PX-=+>e=_s4D7)D(cD&n(oA)qOO6rd#V*}a+?(%-?H;t1$~Mu|9q{e zrcY5R8?U1VOjc6qrzOYheZm(%kLs4$r@7RnuEs1BP4&$DTvJ^)+ij}bW;=acZZX*a1tu;pM@Y@J6yN^>S5PuO z(N~9LTbV048iX|SokzqkUJEipgguU@O^!AXQrpYje3BtiJ+<9pC*j3h{ z3^|POu!_*AB0OK4!wOg|MgJ=;ML2E^HHR4k8KxP({8=y?Rl0N#;W*~Fwh4RV)3sSM zFM^O)lPdeEu@MMDgk}}t*|8A_LIn5U#75zqjMY=D(Wh9nDKQT6(plE5}-$VstAQ=I}iwp<- zOtD50O%rM`1rOG@T!M?CDDW3@;eObTj2SOl!qt0Rp&{VWDu6OkJhD4R&d9j+=F@Xh&$wawjiLY}?6CRmlt+4R3X?}@ zO!iGQ96+3KX@)I|pre49XbCn+j74Q@Ty2Z8*Py(Zr<9Q`7ngAoQ9q1VQQAo zL|Z8ejN9IzZ8UNz`qanArL9ld_VTYb+2m&BDYRAPH~6Dss~*$x-UM6+19n?^8X-BT ze2fOkXIf0+3I($BnxXV7fZzP(U8ZcRa|y0cTp}lZFs|R&WDcp#1^2Pj_M4xTjj5YN6L?yYn}O%az|*X}gaSwYh++oM2Tb{g>Bj@Z zZy~HOYF997tYijz%Y*r%b#()-Nfy7-g=LV(WbvLyE%K0yQaLA%c=2rCqYh6LT#qg! zVot2Y*DdqdOBMpj6O`;HIMbCt$B&j|7AX&JvL>ttbV8nGhCPKcovhU)qJ@~)@0v|@ zdbxFVGB@2YmCX$KwxFY@9BwJI9e>6|lvuX!NoDn9{H%esg1{hLnvEnljloF2ZG{>H z8pa~nup0R!+uE!eWt56i!nO3cj9TP?`mplb6zaHp!&hf0)O8;SaT@l%z;2_$fF*2D zal_$N709x|$w%X|k)~SRG>nRYjYTCBD`kE!T$V6?P&$#z;n{E1a8Tdn_5=f(J<(35 z+!Tv|u$ofT;cN6V9{PVd#<)uS+KkK?c&VLPMa`;xN1?{|D47Np1KY0@%@uj5F)TEb zcGM0JvG%AW`59Ke6Ex<=j}TaPC!jU6EXc|5loo_>*g1yor+re|C>nVJa!&sR!Mtek;tKMj+rf)YK|hD)R?kH8pBw|)bjQWgJ^O83 zbz3s>h3LcVwqrB-pl)l4c#z0|rv;X=FwtR(sxdQ0(Iv-^iY|Hdm@bKt?t~0i-EH8Z zzWIt*=OM7}!WS7+0RN24mu4}4rE2I|jH+{J;Pbmn&|N?0Mlt1$VLSOPtbE4@b0Z{r zfW^>6sp>4H$IOrsvL0gyBepa2oNzA6Jo|z^qpCf9QDlXwR#R|0NBxxIPdE84EJaH5 zIaS@PYSzKLYB&j}@RYLSMI$rNXV#K8ibbcR^RP7?&4+cq|2}qQC#)K8((a6WDiY~4 z?OY5_+=j4P0IOm?JeG^WS%t3#4K_K?HQ-LMGUmIy5$%cBl*5H#l_Pk04$Lo{r?X0e za5oR`?&-su#uB(nA)K})C*^T%9FINt=#I`q<7(#Wy%7QvZ7)kh`tH7Rq8;zq*+KBnV8yH(LBLK zI+JY?ITh`Zg__SL*ko=Erbr4Q(tlE5#tGArCo3=EpY< znlKKc3y1Qc;kIfUya&%-32puhbxa6t{wM88bCnIL8@<#{(2_K%-jXEsDj+!K^uGv0 zQpS`%D}8%k@Qlv@C&L)JA2CLMipj#LsK1yP5Bz<3X$-c*e`r8epuM4cBzZ8s{=uT+IxXL3zc zJjLF^$>F%mw{Hu-!nKH7lGyGB@3f1YMjB&($1TXm9y$^|P0decK9^1AruoW@To;m( zT>uhOt26%dRFCt{4J-0DXC8Cc@T@W9|W4Jd%d=d9QC!x zVi#V{uTxROGeDD}mw^^gz7ljc60>IEnyED)wrrL~Qqa$gU<(t^C&PAnCt~5Q3v553 z%c$Hze;FqO#3eUqNs}ml0=o+tLz$BN*w|r7m`v(aG~2Pcg#cgMq+zP`I#|B1Z9b0; z6$}og%Fg}5GgGSc@^>qScW$Zui#@8|&5={iiA-U=&kO1A*x93erI%5dDDPdZJy{v! zvrykS`wM22pQG#qCZ16`Y&k2U(wVA1l1Aj?7vg)C^s#HCW0mjSWQ$;Wt6Ymn$=pZ$ zDP{OUsyLk945xA(f=YN>8J$IK=#f=Or-GAgR$SPvjDa7!y3OHVWx%iOvz z@Z-Ob$-$H*E;V^}m8W;G2XS%i1MJRYQk%jlWZYVjdyqe{KtBnlSF>_Gt0U%)uJV3T z1|c6*1~*)Cs>A57%SXKwNGs6|$Rm~8M_Y+VKYue&m44u>YV%55XotbX*Sw6liXp_y zAO`7H6GP8t>s8xEBUy~T&!S$?Eh^HwotEquDKuf@soov>6=b8piQN8{*dax%#6sgw z5F~rIViW~pYf3Pj*s|6M6G?B71#WSpkx^1Xjtx>dOy#VxMSS2k40axD!#G)Q!;tQi zmTee*jUm#qyU1!$VGk@Ohp2+dS9+8m2NN;LqH0!{8N?O(wD|O?jSIdc!&mM%#1{XI z+VWKSF|g0$q$IyHq_?rdsat*YIN1ZBqu58p+*LO=A74IZm2;b?iICDt;rZ>j|vtkZA~N~)z2 zym=bSHb!H%6O9iYgKDpj1xq8_8zu~_TujJVY8hf*yw@kCmvV@zi$&d2k*}lXpF_E3 z4f6)EG@g)(5!$O5;k$IJn-s`M8>udk{G&@_!0p?v0|Zb`INAHgsa2BTqNn4U!oq z8euDtuqV{Nl12)P8n7oGhXC1ViHTPA8BF4q8H>vv%}i-)gvZq;P3me+1Ok8LT;W|< z4*-A357=VwE9C4@{z^RPehy-VoQ|_CF*!#41vMy0W(8+dp(W;K1`3k!1+{mg@(=>C z+tQLM;0bIlS)GrI*ZLpS>+vm@uo6}@<_wjpm8e`helH0TJw9|I<6q0x4yuN_#pNxJ z^H4WSaKj+ra>6Wh{eqJfBXJJL9M)s?l7l1FdhTA}0}b31hK0yR%ZcL-rlBF^z1|7N z2lBNen870uq|xs$26h#^oMpi%781l-SEq6_l;7V}xhJx=Wg(>@p10!3oZ}#plkIyv zRbjOn$sgAcEO9(hSc>@WUpE5}rK6?oYL_rutD+$kVzyHz9_z&(NVtixE== z`7QB`G;(Ng#MNq>CV`(j*SkVl9tisfULdZ?P@gVfjxeZ)vV?uSmH;|aYy}Es+8^SP z+~XNS)_-qUmI|?PESxv)_O2nW($uq|d~wJHQyw-IDVV<|r1DDCpyTpMV`ci{ zq4e}b`teiG7>oH^<@pY+V~o#0nU$zKDr*T7DOEz3gyOE0vB^QrXNPKT_WUPuZM2=L7;a^adsv zN4AcMY#lCIZPXFb7;-~2@_UpC>73*I@5rOH59*2Y5tX;;p|(a_dF3DQ`R{*sWLC?t z#Q3E$$xW*~I3*WdxtwyCi8a!co?>;wi(6nmp&-35Rh+m8<>LLkHY!oE!XXJ8$YnP-~Agup0Y`6rPV zwV?sN_p}d6wY~9=HK__S?Tvq`g-o>TcL#BKdgIGAu?Bw)tju8oac}jXQ36h2-HEA@ z97w25MyP)=xXGT26>!4V9;fmrB#=eHG*?@&1kAVR*~7U4%Y{BSHu3=ShWy~hhUt>( zuvhYYlwzL0Z0(7U`~hJV>|Mky*`Zt=EI7-Kbbm*{gNUW?=Eo+)s@^F%!Vsr&ydSsB zZG0SYt7IMg2JkkA3B>mE3Kq94Ed8y;5Ldg;hkt_*wfDU8a|U9BR|#PvF}t0MUnhRK zH@3MwkQ*C z)$6oI1REvzhB>Ta;k2R68(&3)(B=&sKZKHDE%-FRG1*zn+}jT~5x!A6c~1EnxV#m1<1Wxe3SIF~zH$$nCV*#*9*)DgFsFD1@L?6=*jwC*-Um;!8ylc^n)2EO>}8o)+7*q8 zz^L-=qI?relNc%OrnB?R)--k%WU}d&Y&x4wx4P+cTM;Z0XIj{Rt?5bbM0Zm642nCG z-N~?iPnJ19H=!ln?q>7L-1d&+2NHHiqO=@Mm+OYVOn-y&x4}P5e_Z(;+ie$@6vuLN zA48On3?y4wz5wohD7!HgK#vZDbEN>F3ewyR-+oh?VXz+$kcIq&p24hl%l6w}?{d>F)G`<#yx@ zcY1mY!`-5^nt9G}+!@kPtFR^YVsJ%whO9f3CZh4WiZ=qSAVU}kXR1`Q9Cuc2s#)Pw zr-xH@8E%(KHQRA#*QS~sN`-y!ZndwcyR}ayqdA5xaWc!K2KIspA6)ZG#}6N%h}Y>SE&whQsUbSeumBARZ*`Mw5@ z$cA*23z?Sr`)3nG?C|%AG`NlZkGZK-YZ547Go3BMt5ezu$mNQ_J?P_Ic;hEG8ITYY z^F6}EaI_Q~tyH!!EibwCz4zaTDu6X?5!aPetQfBYYx~!*7e~#J3qgXfflj7$C^SYc zXU2qqV5^6tGzM^whByYj9cx>a%q8O;r|_;lt{BGb{s+0<<3tVv&xC!hnol-WOabO+ zhB@4c2doG(0J3XB4t8IhnHHZXzqC5Kx7imh-;$2DU~?JB7t8Hw;%t)eGqf@IS%47_ zH8B$^vUVSCt!`uV833L#kaVLh16-<&=28I65w{HiMDoOW{QmDChW9Evh~477z;6^` zi_!Ri@ZkPpG7?6|?EREe zqs3M9rkFF&i#dM2&3J+j@`x36n+w>U;m41gj@vBJyGPMrXtxXSQH`t<;IvN4ZHkt5 zMwjWv1YaAAB6$*e(q}+bkoS~rM&O90-Q4y#71O#}5w^{317X{O)|uo^>gep9gTL|K z8Tgy%or=Gw^tR$}cdv`Tb9$3Zj-6|+Gj{{2G%>fGiDNXIn0VSHzjn#5UGi&}{MseI zcFAudb8#oy{ny7_*Cz|+Jkrmt($96%ihdcE+Tu>EwoTT}GIC2ba*LZ)kz3u2bnooE zligO|bv$sNw-A5)vZiQ?x>M?!V9LL4f{fd`$tIYH2FO`J7o%m#Yha>|-RIf5*r`FW zQ|pSIx`|>rZj+I3pnG0|MwM>UXl2UJCnG1Ihs?t|RKf?gY73-A(B8g5)Mr zL^qit<)^UqO^|%T2t2>d2{QgGnGEI5i_z&W1+HfKr&xl|bkBl#(lS8iB!KR2_!BL- zqF-bz*(c*S(qTA2IWG)%q>GK^CY`<5{L&F0Xyai?C!9iZ>c7d!&dZL4_Zbc(8UK|y zsIw!%b@<>ORc!dao#d0>o9bzS(y!|4mp+vXj`FHdySBdp%Mu0?L zgheHS9am!OvaDq2cdvCvA!+* zJjuxsIhYoK^DX)#bmHpxT%Bka*`Mm&@}9=pNxcASpSrm zo&r_G#k`%^lHD=;heS1HJ1}T_Q+G|6BG@Kubxbg3%SB;5_Q(M2p7|!VQ&9D@_lzD} z9U)BE>ewJ5>KJ0Pt&Sl&wj4XmBL=OUQ1=i6I_rc2V$9>$Q8DvbTV3Zh1Bl2!Y3LrY zQozrBGELA?FC(uAnI&$Z93g&@>$O>elNzjtJbNPbNjZa`M@(mg%PWJY!x@syaO4(> zCpPNvs-9V#rIkWQ3l+C_VWdTI$~7iA`Qlk{Q_n`6C(k(8{IDgICU(jEa@QGwA;HKm zvDy1@zfKsX-;nUu=%hmR+={>%#b!4mT;R`^sSWfZHfT6X#Rj01wPy zWuQqlFNgVZw|VoLw|O(0w;2Y)1#hr6iaHBk7Pk;&0W3Cv3k?>C{+jYB*U#>jaWjel z*VGXJ`g5C4fJq?0B)2Wpr6;>>AV8bjVz`sT1ZZ<7X#%vQvzQAq5R#-@x4NyUckAZW zyLB_`-3ptW8ln3`KuwfqO~+Ba!bAnD?t8$V<`3v%E)FQDeQ4)Lj{HaDY8O03-+#7V zcm>UnLtr%tDlnb?#zwWL(NRWd*$igKOW;chED|t5|6`&9nUQTk*l!}AA@D!R0V7a9HTj0;D}mZAqSTg7V3@4 zPysvUm^GFLbiYpgia(+foRGzk9;lKw?#{j$g4*%OGW ztb8;ZxO72QKBQS(_}&MG0sT5AL;Fm1boCAwi%13YmUMLRf7Q_`g{2^e)cGRd;!lNe%z=GA2uCiPs0|BL;#IZ4P1I)+qDp-NiKB{SMms;KlI0m& zZ0@!+b&r`Dp;7j7@;zxMY^MMplvTt7@WrOy2--GM+=}d*ZqBciX5~=f9 zdd&BHkshqS+z=GxuYO@Kbr{Biddk-_dq-tIrtLv5P!WxUn=pAfRVi0L*(A=;X)$lIv?6(2Asw{Ln*9y=FP ziBfYh=IiQQ+yGB?D#j-BZqzrAX-`kXmY#+!Jq=r$ANg@$w^jUTGCHb^-!-`WNw^Sa z%xloMddefHu7cHfw(?a78vpzPQ0Ch4LX638gAUdpR)M)il?=8aURZ01uu{Ll07uP| zmPjii$8-D?T4fH8s$uy&3KUnaD-516Q!5&3i+cH5yb>(&#=j(1LF3Wxt69UEh2=zX z5A>kgrToQCt5ZKG~N zPOl|G+jJ-pO6wD3w%SyePN7UgeMMYp&u^+mTtyP-L= zR|ULv-16EoSUPP(d2Q)QL3vm~Rrya=<+Z!*%HOWagQ2{>yh+jSAFJ|yh8h=Y={Y5n zp6X6PAyd**gF>(vsWP3W3h8h=l)pn2f)&jALNF!O(=$vwtf$5D7L3lb{m$O$cB(8p zRS{gl6gp0qB2wk~7_n+hJGEs^aO$~CdhUeCr_tN79jiLmb8i-8*NB5(BYqkDCgazM zUl)E`I`7&|K8f{G@a;sIDmz~uy-43)U2 z{B6eL^e24n5|nu*`C9i{geIc$!A3Q17)UuClU6UMh#kf6a{LMZ{s@fugNd}}_nQRv zex@L>Jc*RHWYiFE?M=tqypMw+#iw|WqrSy$!BG7bNHE9N)va44Tx&}27RCEfmTzEL z3Y3(((@p0pC1S>^b=*j?F{G_bR0;hw6fAnm*Q37J;kF<6wPL*w03a?SR)scVKXe9~ zTNGU^3Nx1;vSen`-Z9nwA?BwzB(Bq_V|I3ZyKg=UaW0O=fkGTtglH7Fa3&}u6$-o` zMEoh$%b_XNqmZaa;r|w;aZ`6AOvtv|0NbvvW{4kiE}+R+RZ}SwGyvCEv$4LKn@?#l zM`%4j!NvNUMj^zH1qGOo6bczdY4Y#7#-3P@g8gsxPSq|`wz(Re{(q}qw`5?fYC%ub4neJxcL zvC(H*Z!F%?(c6@p3blYY;V1)_?+Hv*^?e~c3@2gC)J)ggo{o+X9)=6T!_W(LqE0mo zNnVPQ6eD3Fi(td`0@r)b@QrPB`;Q3;JQ|=SUNi94V_Ci$eQL8rsfC&JB6lGOy)eBf zAoOB)u_EkZMd&5&66If_2))Q%R8Qzd^@QGh6t>Bvm%7^kg>BMH0~EG(w^c=Mt58_x zE>r$x3WcTa(s~q@)}vsK{D5lp>CjHQRJ%>u=J%koYO_fL2L_#CCOgBd-j3XYa2iW& zpS*h~s~W@{`5`krgBhy80s?(&=a@ElaC6&WyG(kyyB!FyU3z&yfbHGwRfV@#1lYmd zLHT!31X%7auP4CrdIFR;8-*P+=@ssdKw-!9iU5V3+?`aBJ1G=)c6V0(ofQfz+!ggG ztf)sJOtSyWZLmuwy{o$m2(U|f*MI=Kxx1+f@1_W_ySuyc@2&{2tGjDG0d}n?z~;BX z9+~t?cMqVjM|x#|!k+G)s>nSR3VXSGDgRyyg_Z8gdK6ZUL7}~ihHfrK2`|PZzIP_Q zkGnU@+B>~ZP}aWgzAEp1RayJF`zil^s;qt7ed^2Fr@k!eszzirM%g0j&i)Pw{|aYZ z*<8egnxE(U=lEATPxr5Mo@8E5V}D{|qt*UynKcwA1Nz+;}>rq1)d#vkpl z=x)iLkLJw9^KyfG+??WT^L(<>!+ne>x$&ZzQLiFShI+EI{z>uki8${7igulIUVgIC zJ6E90%fs2cNO6Psi8AvXKK}Hqhu2FG`;wcJlf1(uAw#`kIE!aV5TCP~i>Jf+2%ooJ zq7dX^SAL{;xI{_rcJK}!wpt=1R>3=%sj-z9{&@cNgXbP=b$pltswUcc?I&E;B}P3Mk~m zE|QQB+wwy`ng}INRNIhe^u#B*T(85#N8_c0?aY`Pw2%8#fA|%c6PGffnb!8C#T=Cj8|) z7nqAg&gwk>uck3~vDNv*M?M*0_d5T;eInif3|b209ZTER-pJbOe6{z-6TDHFxeyDf zyzAgB4oc7+hhA(j^_zcPWO%p3BxZFIihtcFK4W=bfgR2t^Pd5P?V0ll5LIsaCc{|{B;y?+LF_BuIc%}RCFoCITifY(nBBg+B&s}z<1J2vgn9Y+r8~=2C;Z=0b&(@DM3$OdFV9aux77qr!kky{_>lt-W(~2An!!xa;Zcu z&);jBw^V|d%6pKxoGw8x{_gz_roQYIt7& zp^Do}kh$|gbA6Irxb@yWL`NnOWSH3gf|=eel1gTdL5`tjNp<#yQn$AOwq>i6sl45Z z>08ox=bZ6=j_BNaRaegI3qan5>=h4*zw+PD_Ia@LA~lQmCK8?NCFra3+P5SQzux1J ze&(`!&!TzW!-4}rUYYeiN1}fBp)&@&PfL?CmG@`%h7%=d=!zfAXKHuhne)ApL?OsC zNRr(o$^&OUKHu9>YGCKD-`e|vG#*)ww>#14mRS3}x8mSMoL|-S=654bl0MBY-uLAt z#J8m*y_h8$+4Bayv}8+=*8^wqDNqOV=MG<9>_Ievlc~Igq}k6TXzEeRmNNCs>!vUD zPLU=g$a@mZuXu?>kvY0YoH9p$lBrJ<|BqUx?LZu+E^J*cxhy+XF8_|JN?ZH0HHw3|%!B!2OIDQMv2 z?v*}C7JY8lU5L&T?jbud!@+_6oxIni-UNB)5S@D@)hEv0u(Q`HK}_YH%0BV~2@>V@sad?djbr^%lWn zHgj1nQAPS_4pMnPMQay(#7{wlsb3nn4YMie3xj(Buae$|Nf}Ag=D<{50)M5 zJq&_co$NN=E5NMyykNfO(9@3b`_2g;x$p>|P$ED9$;tCSdoYVxc;4{A-mj%T1bJ7G zP~VUYUR(x$hhQKgQ6DilLxN6RK5!UQZ~g9uL%sWCUL?qyz>#vOMA>hqafC0#v3Tzv z#B2u%x^BsKYe=YDF1_GL=F+$B`Hy)g$XFoAJBmFJVmB(a<7bzw@lF;Tn93Vu0TU#M zFEcXr{NMj&jd!+CgCOr&MtMg1^KM7}=xFae$(YpeZe-89U4kf~`6PL1@i&eqI+W{} z;RW}ed8}tjDiQU5$PCYwRA(Qw?0Bz1%3><-W29DDf*$(%{1Q`d|7=6adrD*pg1nnq zncXFdNQ<~J5cOj5-ZEy4U7_$_^yCvCCk`S&B9~9Sd&G#hSlX2!4^L}GigAhhhyU91 z<6g707*lyKvVcEHuItV?sX`nmW_lZiY6K~f{$7c)`zP0rdMhOL=+-ZfdK5vGJnCJ= z%3O|EMtK<&Hfh|U`=GS-80CWq;H;tn6#`gS4fr|%90D4!fdCG84S0|MGI0%fJX=*i`0O(2_UP}fa%=;kaKFl zasns-X~1Cwa3GP)^f;#|hwUrU8E^fHQ&yw9f&6ji3R`2%uP`0f!L4 zA+7;K1W*LjfQt#BJf;Cx6F{!70ly=F976-5Jpgcu(}1lB-~_J$YX~5l)PQpdAUo55 zs|cVtqyaY(K;EbUPZL0qRs-H9fW1)zw$1@S`9TBrB7l>J2CN}~Gq47nMgT{$27HMC z%6}SgH34M)8gLH*98DVVA_0`HG~itV$agfLDGvbWXAP)*Jr$#29%E2&(lJ&NK<1zU zpCy2tNdvAYfC7~U{Eh(5O&ah90aPJ0;B5k^3TQyI004zf4QM5Rd|v~0C4lOP2Aod- zdAtVPLjWaW4S10N3b`8aJ^_@LG+=r!0OVyFu$%yjKN_%x08SSga2f%e!8G7v0yqz7 zz_kQWY}0@z37{;Z0cIZn)H*a^76D{=8n6QaoV7II5CX^_HQ*!yD4T1*#RO0p(}1f8 zpnR$UcM(7dQ3GBhfb)P2%rG2 z0S^#Bok;`UC4jSz2F#iV00mMF*p&c^pBius0TeGa0B@?E(8-BZ18yLI^REUxO8~`d z4S1ITPKg>Yy&nKdWg4)Y0187Ia1a4h*fii&0;mdVz|UL&I6G?qp3}zs0&z?O9*40_ zlv*E9`6cG59;0%P`rNHPcd5@W)aOool%ZVR!Hej(6EqLjHn@LYb(g=w@~Cs^^6n&n z>VprE=ch2#|4jHy2)7!J;{@F3M5~D|>o&LnqOk-YT1oJsc?2KYMew0P1Rq*L@SzC= zAMRblhei*4Xzjpg6Ha^zbTi=b<({MfA)%@Kh_>>{U$+`u_<5zs+_ zV#a`lk%NntIWdNGiU?1JyUMeKbBZx z+(4dU2iZyF7_o!gAWpG!d=wFjXG(0bb121A>=|=JXcl|Ml@xIfmRMqA0KV*>Pa zLxQB(H%^wk#eVS$@GJI6DluXt1z)jW#3hy(C%(&Amc-(~W}af1po-`lA(WmVMu+c^ z29HWcoJT3OiKSsbL6#uzM&w<*R-%Zd;Z+fP#nM1x7Qd>{fOO;=c`2aK$V1 zMhqFHYyuAmI${PmCdkVhB4%sEPr-+IiaFr#QV%f)C^L>|{`axg zp2Q}1Wj(|U@VeAPEC3y(f15-RGr;|lk!b$3o)zB}G$^?(QUiseqCZeXYe(%UVGt8B z65z@K5JKz&-iwq$ zKT+?`V(bGYHrblDvr0&v{jWk-QP&@2tgR(TRBPZ%sfzfD4zE@ML^nT2kP?NP%3^VX zq!O*WQAUR-+}{>tMB)BE5)_}5RFo;1r)bqLOBB(nX$|oe=!*a?UL}4C6w<4pWq7#5 z4N*i(rs`NcTcU_+{Dx4F)b~zdJ(Mwq(wIL`M14h-LsOYnI6T^>5kSMVW zq%2Wlcb2?GZ#^5Vyts|Tq8PBl6|bBh6YujXM_JHOr`M`^@E&DfazITLYP^z4@>O2m zNma!Z6%nwmNI(bPMp8``s;`iV(F85tb5k4v;faGPfcNrLagf`_N;~S@l$ZB8D;)0g zD+7_tkQ>p)0WS|?zra|$cDrc!Mam&QV#lQ#xp2JrJM@q$p+YStx6Os}4+ru6)ezFs zAiVJ}1bfX#RZ8O3=*3Z8(sL?ER>0&+*5aYzAh~?jmlW9_{pdnL5}wOwq^RT!i7Vv`I?MpAHA{mi6L8I%uzOP?15`>!64IAQ+eoprz3B(L&Fk zta|?Bb#f(i@l!gPL`Ms-KQNipg4azK53G+OLGu~3tqyuJj1S+UEzZ_KPlSU87)0Bs zRB&TBsGmV^fOSZau7#9^6*jxbxvC~eI-RcId21%AilL@LoXLI_u+(oC*VU($Y+J{GU#v}L}cU^z~V}RbY}vjhQv#ZJ%cf>&@p6$Z$Z!n8pKu4 z(sY78<%8t?Bgq2&RJ{-+i0ndy0GXysC{1Ou6*}1z`aGTQ&VKCnAogVKLMGiwju5n% z&|s1dQ7ouUdl8Xw-XMy6rcfT&6U@{I3k73$f~s~nO^XJqt{h?y=>2V~lj!_2%)d`` zKaOahC#!N0M`(-v0d^6SjcS-Mj|hgzL`K0_QBnL6U7L%`DBNlwRP_g2ZIot4;Z_5Q z;t&4XC{2t)W4c5k8A(=QJQ`iTvnpE+B#PgugD5h(HIHgw)KhhC^--nrDMsPt+ssW~ zC0BhUL{;@Jxs=AvQi>yq>Wj>=gxwy#S6V$L-jc08(IAjLr(|jG<$Kd>2Ht((MNLbF zhNv+P$b1~zbJnlv^gM9wM&`zU-Tb6CStm<0~Lv^qFYwuw1?dDL++I!_k6sOuIo4wf}bC9UmtQm9CE)Fa>wRZ z^SM2Q+pGX?nBA|C=sbtHn<6*vCC+w#4?x2lg^+3CwSr&q6CFhlwv0w=^nUq9MtujW|?{u>u zbzOOpdq|85OTq)_y&u<3qe7gr7Y-b5RHQgRinJSV32HrE*ZSdQ*EYSvm|Tu5q_ehkMe_NdJNy5wMKS z4C4M8V&#|Og^ZBD=9!Gk$<=s|7Vd|!a4I(Zifr|n3_tay$PJ|sMR_wGG$(p!N2x$D#+{+IqIOIq*zkKGnVxNp)?ff&-eDJ(J(6?q{XXh#t-Tw27JD5_ z5y5Bx@9k3UPmtA9OFsR?;tCmajefTYQC&V0=ZclK#Oc{#d6o80(Cx$2v3j$NE>0 z(5Nu~?1e^!`KqCIwDRbnnlUa}(Zp=~<4ux>2R&mL6(V09^n~FNHK7^Dn?Kk&-p0qR zcD((LSz4p7@Dm;VJwG7GtN3!f@oQ8BT9KF)M)b#9c-a1d5v|cb@)I3>jUQz5CwxIi zC&G)wejs!t$ba^x!f4qGvrG0D#;ANog;@!)7H%tW$=Z)A^pfF)XYap_h=Wmy!(6M^ zrSo0*N&pHyofm>i3E8=iWgApR@)|X6S>HN%u53>ZVtK9%=gu!e0et2=GfGz|AKY># zv$=k4tpC5ENT&J*l!_fQ zDx`NwgqK+grlR@*)GTu-M`GQ8X|OBP^U)AT_%o|wjlPZ#7z5|cK6Kl7m@kNYnKR5a zUXK$xXnem;k^?Xbr9xX2B?Uu0gO7F0WJh(d4x)+_0qbipkADyyk?}!@H&raM7&p zEo*?sdr=FwVWK3KCvvO2XC0cOW`)|;mmD4ghCt&XV*AbUu}#N)nyQ><$?_8KXYwDu z$34C(gCS7X2HjPAg=Pa)74;+)XJQ?thRma!FI5pb-1VgmT1#9lZgiIiAQ_@ap0lh! zC}33BFBPk9Dgv!Y)Cyy^t}-f2FK^dm zdZroOPf;G-N_~y)PXNCjC7*5q8vY0*HS>-sJF#;UvV3qVCeKet%Lf7X?!9PDhXF|+EX|tGRibQLX}xee=*|AdOCIP6n7gePk=bE?M3F|w-Ubt@mqu6+4w=wi~IsVh(!@lK2l8L zk86)?fSspuG_!KbCvhw~QgKnwBx=1 zlz6)+KDuzts+w#&vN`s*B;S z1;3Bs2Dwltd&cZ~Ch|*LawL)olUFLq@JE;D^x@U|ewWsgxI4)}0H<3m=34|&*FrYhvY zh-Q*;m@06gUbdZ&lUc4yTF7tm1`>ltn^f86WZVbHD?$L-#O?pWsgng;Qt16sYQ?-?<~9Hc>J5$2o|Pxehdd z0o7KEzf^Yc)>5-A*Q8n9Q-Ma_fV5P2+P9%bBb1pyUpItW0x#M6biRf{($|qq7aN27 zp|%Duxt`^8?}6MBRspgy>CkMHuTMu#NSgUH`Uza6={T&Jx5bUi*saP8Ou`LC=%bOi z|HLHqFuWp)yT5UR+#B!=Lfi()hUxaN1gc<8wZ^~d6H}Z#2DjT z+r2lCEVS#lU2MMXVylb(yH_Ge;_T@=N~mHYCU_U{rb$fAJb2;Mzf!TVC7tSQxIBH% zC*;(-j?oHC?N&Z*@Pck}NAaGb5Dr$m^AWZrhBtw*QJ8IoIXvpwk-pi8=y(rAwr73q}SPo%4CVWNhQhfLAYM8oUt1w8IbPt$h=P zZ8M0{Loe6iu`Af;tq~?3oQg2zmvp>3PcDL8i**)j8}Li&E4+CRjxlA&K-=wlpvK+xSoo32|YgJxmZD9;iTkgUE;19{6g5SVou` zltzp+Tp*Z`=iyCt$9X#ksp%$qczLjN)c(7RX9?Nsx>J4_MX-~|rNDfDk>rs9IC)?d z2=xbqsdo`^b6>StaDjazaKQsIAzYf=M&N?ina07TsR!>tH2An=@DR}7P*wu35N0qT zZG0G|NFOgjsJGQ?zqvuh%^_3a-3WW=UW5oUb(>-Ez}qxn=|*haq_M&sDy`v%9fMw@nrnC)eI)0-ia;=6vUX!SY5mjbS+xe)>SQ*$mqmd&} z*3BqORWD&!+M8_p$1v9y(*87Tn?n!l6qEtjBKJs(u;o#{6@ggG zD``+U_40Y$Y58KPpq^%!U|6j$A2p?A|`p!F*YwN@@L?6TOD4l=kA2qP@RX)qru5c zi2~`Ui&!@zs5$}VPj-e0!J1CxT2Kj#r?un4ryk?bJD2SQVv3FH1vrks&d&Vn5v5HP@hl(k!KTjMeYKQ8b!idL2W#x2-wBnW1pdFS9m>>K0R36t_mwzs| zJn+z}!?noq6D5Oq`RCl|@W+5j+78~jLkFl9(Eoi$_{$97eJAh^A;t%{eJ&FldND*j z*7FR+|3#I?s(LSvTfLzN(5wx)>GAqzRft)5B*yPs<2USG&aAcb&^rqaL%%}-vpz*n zE-J9ntC$v4yKe%g-~X@Sw57u74}udtz=@w4oG{)$pg+7eE^_}L_lH?)p=goXf0mlj zgPQT<*NpRFFY0yI*nS@BKGZVN=bfQ9bS0I3`ER9tvp(&~DEDrZtKMh?L!mxDRyXD5 zDLm-AM;6b*Pz7qpE#-5)skDX1bJZk3PMnTbUl?+u90T&tENy_StM2Q<6BkiS#;gw0m_0E!b-+kd)-)j?;){{X72(-FJ6H^_Q)3z zr7{bKM3hImHTd!eiu}87ArzRm@TbRcl*C$8{5mQ3YR-p zC2cWV#!67@ZCfmvsbkewp6WQZy9;yG8d(j%-S`P7>%XSwIN7+orN_P?QI%=MDmTGO z0vidRHAJN$aY*8f+?|+*O~%y^@-@@#2iIlg$fJAdIA42<;!#Rm`tJG<0CwRCQ}xiB zJl*$uD44-Q4DP(1hGOwNSvrv!`Wv0l!z%`eSosU8>zmALAT*xW#^LHs7~)zPWj}{% znRFX7zVH-Dz<~8kXLY@X#`zK2Nb#gklNd|WxKRu_nanUTbS}C)2CRC!$((TTVG}xC zc&YEqMt_@CZLfek;GkdWMkEvU#Z;q))&lbBGm(b>tj=%ud}1zb=|^3E>x5odsp;=6 z<_@+x-|AklxVKZ?+tItN)p^)XlZJcqx$R-h1v@gtdMd_T@sZ(HeUx&mK3YJLB6_r>*PMp@Mc(>1F-fM4K z+u`B9I7EH?#U(Sm&xw`yXE7F<=)#8OHOm~OqhQO%U(!O+(%OV;jZ@% z_s^`|YMin!4;r2HUhnQKX8NPI?dTmQ8T{qsJ$LqQ5hp`EaQ8?tBN^=ZZ0Ft{>;%X= zbJ6vCdwWSS%*#85vHm3f9S2(vVxF(vx%++|EKrEOY|Zlhy){9o_Zadh-X{LHZ=HRJ z_qfD5;HKuoypyD4qUya2Xc2=984R7(e;o0>@PrqSX5Kr^-05SUCsB9X?a-q<7@ZJ{ zp)K(0I;GO zFpmIs7Y*2z09IN9P9lIEP6NI~0E^LpYYAWtHQ-JH*r_$(EdtoJHK0%gfL&7q_9B2C zR0Apma71aq_XuFSYrrD}uvcop+XS#DYe4HD0PF-BkRyQIUIUgAz%Hi&hY-Mq)PQpd zU@K_A1_C&EHQ-qS*!47^WdQ&j2pX^}0qo5h@M!|b7&PFA1h6YYbHAskj1dPJPU(0MY} z=iwua^-oD9yd4<{L%&7_urT!NIL05BD8kU+lGMV`cUAn13>_!VBE@UP&t6Q-I7En< zFm#+lK$*hOhZ*(1B&u-rAt_53y5i!(&|zi4ok8rVMcr<))F@6{aPAQ`1O90k?`x6n33Z0>_o(j zW15%=zn&^lgVae16>mXt7O#$O}kGjuLOR%82|Lhj|cN_%#o{ z6kip;FzL^WQ<(JA2*RCAWYWhJv0f@?N``?i&!DI?sF?BmlP@*kpK5RkO%OhP-{1Nk?I<3KnHfd8s?GQ0i==Qx=zMd}xg;T^}SAFZ!^bk9arKdVgH&y|&hip%6}c zb>NjHpC5_0^h5ReK@dm;OIOu;X%Xu&O5anT@9IE(DrZ>Ia1BE0SPK^WwH!Elk~x2S z{8&^Ds{|@tUhAc8zk2ekHvex1QD0>K>jUo#^j;QtrPAN1^>Ue0mCBVeSs1IW0fkr| z9HTLTr$ztDR{Oe6C5V*1roBQ2w(Uen>#{%;s?oY27DK~_n#C-uVq)v5QTl2SQ@mf% zUOA9g`m%UsgF-Gr!8$mVRJ$+4U8FZiU>jb@y&~j3B;+1@=4M-?EjVX@?O*;m1NAN( zR%zTu|DZv<(UO|yl4>`icoNl92<#leDFk}(5;g7Z*xt)*t9vHIyzZ=lj!DD%;00g= zDkk<@si=WL-HnPVKDx$Mk+sELG7RiNHD-4&-39~*sZ)4H;Q!?o8y zYb8Fe6;mckZnv%yCWRVcGfYE3A2V_q@V-AppM+1Ju3Rcapso)f5KJ-OMTVdgb?@xF ztz!NjM3yGbxib_<#lRlbAfz8#c8N{aUZ3gtWC+tn0D&pAZtj9@#s@kkbxPXPu=62k z&;J}>qryT2L51Gxl#}hBiai=EE_-3$MBn9Lzbz3FB-e$IbO8jC6Zwgbruo5E-3-2J z@4^>=>+}zat5IPo-HT`+fjN$4`5P&W3QH{H&>2{yh&a7dZP^@BXtYPj4PZUND+$<6 zqxg~1@oTz73zDJ^S$tsAGi5I&7NR~3eT@n;g>^0F7i*7jX)C|0t(Lfwvci?hZRvzo zEE;01tFR2Pf#_6-jWTRC`oVv6!u{GL;kJJV0gpm(j7iRil>OyRS zx=3bY>*6 zjo|y&1rN=Jhc0$dvK6NA6CK4a8J`!Mk@G%0u11B~V<|e0ZqFhHs-287N4~xmEI+7) zqz!5@P7j)f*uWjuTg`9&M{s7gz&YnrdPW_kSzLgl#Eq@r{4UX{aGqX^GYknIrcW3Z zW~*>URz@B;^9wG2JZ;=T})? zg~bIpOWc14=b6mI8l8oY;A}-8vqjM90y_T(vc-6N0S3=OzA@pIDa8@;quB;W?F+DS zM+OVxVujb|!ziTi1Q5~0>c18U)XsnbESO6K?9}%o=&b|3-Ir1WmQ0X%%*y%aYlVm| z!~q><LMYTPy2RWg~`5A5f?y6;esHd#Y@!GD6S7q z01$+bv4$-V&d?}B_*5sV%zA`2?O?r-qpk?`Bof<5eK6Gb_p^ry;qf{af!&+0$VV+q zV(oD7%Sgt9X8`jtPS-CB%Y|u-6u+#LXdbCG*rmy`a)-w@Qqv3-w5L5xNE*VW6x1MF z?c4@c5LO5CbKOQ&3U(s84XQ?1;7jBF0lySiN^#d}6Khx~$OB6>AxKG~pcfyX zcbpA^{pNqAE#(31ahemN%y`X-!w-?T>^n3nL{qqo%oc+W<%HGkUuhOs92+O@$uX1JHQ?bARm70ZG5e^AMRlf6N1V5?CBZt0E|y=Tvg6$EgbCqnph&gOoj zz#c$P$R41={96g=kpqNkJ)g>oY=p;p^SiLw)j?A+r#p{z=4@YL21>=h*Kh67uz3M& zNsk{IiPUIj{m31(AHc__{r8at%u)gE|6_UMkEln81?(MD6t+j$0{Irz64DN_ftXK6 zqe4u^A-~UeKz?HU0py3iVnqJ`L;v}T{#$AKTM;PVk*7lR|4-&wJ@3>t!Tz>t0!Pg) zm!qYP3X67xB{}|C9YZ7X2)YC9-4r#P-Zt}wTxv17>3J}Im|xdxk3Lae?z$o>c0nEHlj8#QCB7WyBI zvHyp=Hvx~MIQqudtGy-JYLB!l%UCO4Le@&F3-HS8L&n&~2Adl!8{3%M1}rSuTtdPG z2&cJ^gd+q9Apvrd3rIo|a*>4GrffjhD3d~=7WO-9F-lC+6Wo@-$R0f{3tH=wA%c6zWD^L(|ls*B8`H{N(>H? z0C5pt-oBtjKb}WI5NtmG!b1!2C$cFNz${hZ*|&vW#Vw;7o60~hMuG`hU{sqF$BeLw zNw+aVSm~6UGA}_rVo{2E{Af4SGoO^C9;vZg>fP#bcY#@5Qg0Fe!b3g$!-U~t{6nwR zwlB|thGw$04A!!`bTjti7?Vil75gt9D z$2_5A_TSE}%?Ku&u=OBDK8hF#b9|)Hr z`ZM_S--yC~%mXx?mhvw=w2XhK;N|ik9y$>JR=%tPTk|({R|G38v*9j=-^#onHr@pX znT2_RA06g&_iSiw%u2>fch9a14l+aiH1G@B71#Cez#@t8(rTT3Ao(^G(DW}-fU+?d zVDI!R@M*BDE0}67+i@uxCpR4>vC!^f5h(|aVwZ_3l+>n zrY2FrOj}%&*Rdv_P5rl@d+TW%aDLuXPnUV>$pt1d=muS?1qWT|B88ymwF_>f0J-}* z)%=HkLjPZbF64Auo0w}1V86a@iv!&Z)dvR_ng6Bcf0g+kF#qS8|C`MJZRYRDv0bniXm7GM|K{~P%A^nFx1A-8iqO;>SgF4hSoB41Vif>I*OrV89Iic^$hhg zw1J^@3~gj+JwwMa1cQvo&P@z$V(55=PGIN+hE8JWM21db=p=?<0ukAHGDD{^bP7Xf zFmx(Ia6S>)xtSrjl8Efw!q5c_oyO3G44uxq*}lukC4;|W6W zy=*!&&@9dK1NynvK0m6Tr`YEw^mCnkeqKLMwah%*>*YcM@gBGA{I2dp7%2Kg5rI-h^&YlcXR zpVYZRhpD7PVY<6#L)0*(^d(HH<&o_@ZkmNMo=s2uFF*InoPM+`%qO{h1NJ+*eFI^M z4D#;Eir^sOipbp)Nf;vnsVCBfH!|o3-Pft+m-`9+e+#mtFfPdkk zo%n11WI>Ygt&!U3q?t%)%4B-0JlO9-*3={ENpI~dLp=7Y>p?qA`nj~jyt+#}x8TU2 z8+4J^kVFI?Pb8s=4D#+qrkcMl@V!OYggW~N(^Zx_7b3IfEqX5pl@>mW$3_?X>xg}S zx~fzf53XIn6^k=2u9y{badk6|6!Q0AFx<~FO6G6jRTkcN`QYVqG0)$n;l&0xtB4iS z_~8AG2j1H}@VXyF2Hjv0yey++{sJ!~#bkjOyz>yD0l^)Mz(qvh=)%W*c4_?>Q*TH+ zzSrLNNM9E(dEmao12^-E4D#-Qc1u5V_2*~-t+s$Z#i9Xujg9!NiEyRxghe`qD~6|; z2v-g-G7+vIUT-2?Nqm}#a7FQ;iEw4{btb|U#`l;AR~moXM5HaG-!KskHsU!GG2KS| z*+k5+5fgeeE;DV!6h@$)W=RC3@x`nsPs8AIF{5SpNq>4)fwYA*y2dC2*e-tSsjrWE z>dQ?Z8FYi5nj7wC5hb&??P9hC_Yxntd@cret_F8@A-_TzU%SBkxZ93D=7HD!ATsC% zi{ND$CG)rJ)@bRyn9zXT_O=nLOhl87=rs{@Y{W?>qS;1lGZ7gZajuDIu@RS=h*leM zlZnXMh(}1JyhM&&piG4SqA8|P+jVSiq9@ouWG0|3%ST^zg&6Q0~dS^xbmBcBZIsb z!NpwtIa;{7EL@lQ;Nr6j*QCWlt*$~Y^4bsAD;~JM;DL*YBZIsb!NpwtIa&tX$5!j! zEE){BuZ`H(MC@lHmYayVHewwktiLMP2jeHGqiX*HbJ62qVhihN+{>7Vq8`l-MAj0WxFz_{8Sc(Z?(mrqiKAM1G%?4ao;`$t}@8Eg^7p;yP zam~ZE8rSi-&cbyCE_lWX!16ZmbzHQ?{sS&-EDI!YHRIx@6Pi}vjO%_}U&O_-eu)2< zaQzb(Exmaj@P4>f;@W`gT3lbj^%q<*q~8k{X1D^mMYxp-z9~)~U}}Yv1Mplz64Z{2 znldt~Ze-Ndkx_e%jG8tws(xftI$k0!Q6jFqL|jFQxJqwaa0j(I(bO7EmCO~>p zS67)Qg$e5vI-c5Jx%rae=o>g)u!xH3s;;SJJE%~J`cx4tgkvKUD<-~;iK)9(1`FZ% z$i$Pqs2Gr)u7#R8ZgzUmUW)Rc%zmWV6CHR%#;lP?diD5YP1$~~-=_qm`<95?uSDEj zZ=7xIG8elN!awfmL((0+m>2_GxTJ zaaH1Sa81FL#x)yP23H5JxwsbKT7nC+q5(|)1u%&hz;sps>P%o4t{ZT@7uTJ*?#1;0 zu210lEUqu$dK}jixSq!KDy}zig;5*jxD>7&yW{>7s2lXZp5)z0@xD!BE|L3F6BvUW zaKAxfZj<{*YSAD8=+a(@78J*ZXAwaD}edKL+DZpDT@OU_1wwjs2DTBc}f~IQVStpF}wOYvevv z?$hOdjl8`5Vwpn{H1Z`BQ9_aeMk#RFPyT``<6;6LT!J|DDZ@KaSXEquWykZMweFUb0-rpY!! z_J-}e3Lm&5l!7Z-Y+N-yih|--k2Ho~T}Zr8Fg5f({?!iM!N0np+xh3&qcn7zKjK!2 z7y=ITSPKz~KXI)wbPdCOUzgH2_kpocU=gHbGNHSeidah|mdf^Ijt|NjqpT=GN?l6n zvJ@ZN@G?*MKu>r%!U_13+`*MES?9tR5*m9^TdNnref$)T$e4;o*q|E|-2*Xuv@Jalgii;ZH6 z-^_?~V(1qA9>T2n{d*)rSAZSoCOwOdL%N1!{!>;0qz z!%2bgr7DtW_*|6IgPH-MnV;;^NiDn>pL-*ElNFVjb>iBnv065`VHsQS8x-^gNr38s zpF()?ZBU+JQ0riH*gM1xX2WMA1w1uAjI!z4JS+rp{k_5I(OtQTC>8F|#NVt2PllH? zL&g}p5{M#!t4f$khYjJbxaGN*{w|G5np>%>Jkd2f%!bvD+c`D}+B6**#5Wdlr zb>IK_KlZ(xbc0zE&5XHU;6;+ha*PZ5y#T$vGW99AX`M0!x9X|5O`3*VA|1y)lrCox z0)D&{ct-*XBhSKtn%S7-pe8m*K_y(8>@ZJ#zk?CQ_Qye6Y#)9T{H571I7mU&xuP1b zf?$GCnp-OY0(m#?WmUF`xaQOlobU^$(g$jAkPUoDataQzl@Dp|7wIVZG|hxv5A1$o z+G0L4p{6{VPGn=zbY%~~!Oq+7>ITsF1pmR=x|tOL*4?*QyfrP>?5NZ&_ADR>YeH$Uv7m}+I+)!VIhfzT ziSMY;&nNVmlXV)yV+J5t!jA$F-Wq_4>P19|shC7iB{V<@i>fQXk{z?gfNasJ5FBt1 zwmY_f!4*ZN;j>Qt(s)M$Dz zk2M|q59L1$UdZ(!67DU3&kvGLWeQ$|x2j6`k8>&;y*%8uAvow`F}io6XD5Bk(uA6H z0;jKN{=~64o_+`p?=`C;TKXlOF`mfw^jX|_6+EK?gc#jZjZ!IiYbvGO+gP~nugW!- zR0LIpu9ONOG&$usKxi@%nmi^#)v6i@RqrW6laOgJ$8Te-eetXSAwT;NNQTcUs69EviLIXh8|H5GDh*poCi7H)bc} z-YzlSD6=KqN|yW!Su)r^VF|`@egDA&94JPetjY=k*&+nmRGVbqCJ3}i_HBYdTM+`C zDW?lH*~yyh9J40(QTqUseJm#9Wy(&~#hPqa?W5Er-kO?h_txZztjTs=lN~|Tp=+`O zgxFW@D}>m$h!Fd!{e%$v2_g0qLhL7m*v~_VlruN!inj*Gc^;Z3 zWtKqA(?rb&RbCS{kCwhT|PKy@GxJ8(}CTc(y%Mfxq;Zp=AA z9WV+;cxx~^z=P3nKiU5PjnLAdTB;FR3WN?yIR^uwgNV>UV;g%rIVQkfhsl(jv`>0yyXwdg4()Xw_ z>3fVi2H72Bvm0-#K1vsZTiOSY7Q8`nc2~^J9Q&hI9KB98EI(-yW@YbMmx)+tLMCEji${9g*hDPNK zaQZg2O*nm95vTX7e&O_f;q-pt^nT&={vu8vNIKh1c>|?6eZX+~4z&YB*kOs_Y$vC0 zSKHGI%{bRkoH1#7o;nZNooBNfFQ=cQ&LvIHQfG}qQ@k}ep5>+K>7?md znxT?x(k#!o_Tt- zx3ku{5LDVpD(xJTO7BwdLiX>n*^ie>JJp4x(y$sHg-Up9a31zjsh3n5)>OJ6s4mb{ zx&TzVDCt~mzPP9~l`b+=x~mFmh-P{CV+%9UPJHW8I8H7ZvH)m0jmtANV&Ddz^Day?PGeoRzu zR5t>Z8}}5I>(vcJFs0E^nL1m$nJeMyYX`R?dlHF^j3B2C^W@egX67Unr2!y_mguZV~LVs9&7+HSUW;tF$-={uALf@{gkR^iiNwUZ%)hFF{_?cSgv!Ll`NYl@ZNz>1% z&mp_d+3d#49-mR4B~3r2J~awW@z&t@DKAa8kfxu~H2ri?eOlA>(_oKBQ_kms%A-W( z(J@i^g8Bkb`NE!}@~HYeQF&NBJPIm!YfyREi^{1)!9h^NYk&4Nz-qrZy>vG*zCs39$!;mCruw$kB>rAyfrvJ?xpETr0L_Dre6)J zuWFip73}e?l=E$%@-3qBtuaygj`|Kz`OcoA@-6jkqVi4k%~4RnTZ76sy{N1sD&N$o zJP}k+XjGm6dpxP06!v(sh&{fmzANnUU15*!3VVE4*yFoJ?D4&%^LYwj_<<#Y^L?_%_tp1Z_V{tF^Au?MW771;W76~|>L5t+)elFZ zDc%|!f9R#@X43SBnx;Prsvl{Z{s`>x^OW-opz?E~^7AoKd0IUURG!{bRDQ00K~#RK zemV*&cxzDksTY+~h{{hjDnARVpJ`Nn22`F+InM!=XNk(QW1{ja^(&zAt35^KS@oQM zs`DB3%qVE#twHMAC^m*)Ck8?OJ2`V9#38%q%9MRNU%>c#Z=W&z`Owa)*5zP}@Ve>Wz5 zUsf+8yO(WtAoj{g#9q;e{XVFEuMzt_xaE)P zkHRf~EaH~e)$777uM4-lF5L3EaLel+ZVC3U2WZiz4%h{Wg|NGxh`9{<0Dl7iuZfKm zO*?qFXil$eM)YDg%iI4CUGQK>2L&^AxEGAZbnUqJ1%=KdUkk>?W z!HglmtIk{v_;1A3f-BQ52FlH1w%jbn$;~EaH6U#srj1GSFioqPhiN*~4AVv&-#Wh& z21z>MykOtAFiMgKY~|8*OM^%LJTyplEYVM%IAfP2#7Y~Jg;k{UDjb2U8?9OWpECktaj6GSdM?X?wQ&S4$_sgp&t5C85}lW4&c{)NDI7z%)gD7wgwckP7||bWTut25(koPD z=T$hPd-wK~6>xk@=@at^#Nl%oA>yzy7#Cp}Z{Dvg-H?rxrMX)X-kvDGz7E3uNqCE9 z89a|vBT`nR)#}P&r zlX2ge#VL;v(hlz3?Ug)B9yk%A&JXKsD`R3|sSlBCoft3E&rQt`%+2w#Y8}w+7P}h) ze+EvWmRLv*dc~m*EiqWqV<#@Q&q7G&FozpRR+Jfb!~!x|N8=2Csj;|QX$@H;S@lbD zBP!LE=OkrCCIa+>Nl4dtS!zwygj9Lv9+P#dcm6+J)-+k53qTfOw}`f$pB9o>Qy9Ob zZ|e$2swR}#+Of&}jkLMvO5{~mD^MhzSOyDsFzYN}2lvMbl%8-)Z|*`%lJ_=Gf2(Oj zON_H6!a}qqq4P%0Rf4OFXXni-)~aDNaf@z)Ep)UcScWa!!fU?)(f@*aVGSfJa^JwC z7qlY6lh9z+8r(+Qhi5jo))p)PWAH zPBeD|>`9BVWi`iDrOUg5AgpgyRAjI-rA$rfj3u4g+*YU}NvAI9Oiem_fpBU{+iaxr zaN9IB4S7v-^XdaH?M2*RNZPWuO>5g4%s!F2-c8+))Ki%n#;7evq1mB;751ZA)3f*u zNMV&JRA>+?Gzb+MgbEEFD%53SDW{Hk(-2p%c|RI|(wPp6)1)&q>CDpQ`4EDkBtV?) zg18AF&SEw+JRN~hqiRIzMmP0Vq@KytGu6yOYKzIt;7= zuXSe%7MAML-Jv=(hi6A}I4m?##J;u&$7`FJ zf}JMofwr0DxEI=aJFB>zdBY@>Rot4>98QzXq|=>r=8?lkAe2{ml$N)prODy*m<_BT zMQ8;{&aQGrY zf03ZS$bMJJs98!r_Zcad>O1cy}r4$9BizFjq#gZEmsT@O4`06V=mMlF1h} ziKS{OK3H1(L03dA!60f0K3*^3UT2AJcSa))t0l?y&-(h--N6J4OcZfIp@^hYm2?y~ zqagoHBlOE!4Ku_h8{&x<2@C)xaR6W$FdV}mi*FjT*p8Ks_t3+3nFAZvBh;aW?ASj>twbHF zd$uFhkpSgL3yO1;ItpLMaK{La`qvSpEeu3kV(L&djYBh^gjH+OIh;=>OKh0%W_h$a z8d)A~vrJQKKAgGGLe{AJ{8|Ixw{vA`YdS0S*=(>|wCS&JzG-|hmj$qGH~{wpBlPFp z^oMjST~Nk~HEZpPrX0g2)3-)32zwTkOI8+Xa6WZcrYzCPRAOwt2||tIcaGU9;5dHg zePh?;P)jo2%JI8R!`yeWUMQC`XU3gKQ7IVZfO_sERjE>TH%ripQo7ss4ynk-7AM|X z8tkR9fLW;VlVRj@3N#P#pAvQ5tcNXB%+Of_vlGURaHL0^A!1 zW5#fA#N6-51J0zP>Y@CLt14SC#Vw@~G%6)Etb_}c zaDfso!27Oh3?ypRq}FOrDkJ+RwsBpDSW*c5opRYzgT>-DQ<%tjOO;Vv|88YGgqGtiq^Qm+;~-Z!4r)z_ zgIEZy?U)n?sSfAWmhOL2+Wz^yt@Xd&kj#2{=sey>pKtyUFA~g_^8i*9vfSsow zy9O3|QQ=G%T3VqGVD%?J`xt6~mE8c^$4~=|7x}7b5BE%gf0XDGcUKkg2C$onYqS;BE$54gEROZ!NG(4Sl2@r`c=$sV?H97y=DZ zBxuMke7&aADsLVH5ZvOiK6fQp6%j%vNwepW21|pD~#pd=c7$YIYjbsvM_}^@#175 zPAdd?(5b~r)5p&{-bu%LPd<|+p9sqMm0vmV>eHHl7F@gC!lb0%8S`}pjxexdYvftbB)O)g-EZJ1xPFkVkvAkqp zydQJ6?A=tTc1_o8T?AH#=#WHOsduGiGt?)yuiD$DwVIx<_R=RKwVe}gEsJAVgdVRP z%S`Ae&;p@czXwADAFo-uBf)6Zb^Q6>4j zQ*cBmeN&Azj2fxy8fh3cx?xn6u51iVQQ1kiqxa@hk37jU%x5z0R4i+xvTI6J_GFB| z%arm|w$hc2AvDTJ*)r0sYk|DAFVq3tTf+@KD|9iu4$>XwB(wWN=xfXt1De;nA8V}8Z3#5 z@~KCjgpT=4#+{XxmP$*PsNr9#_+C8B%G}$j4m7u$4B|RvPEz z<7$SRnw4f(Hc)AGsI=CoS-Y|V@7RCCs}sygAn$_Wa`WG47oK(LRLh#As|qakw%KBJ zup!&veOaUgQZ-0m4H`p$Bveo%h(BaNXoJO1L2ws!#A)aRbYjXe$%B9qRu-zPC01tE z%u^VixDI(zz2&)!kVZ#E#rQ|uKjHXa@vis~*7aJCi?r^opT+(ld#HJ!=}mTtd7ww3 z?0)6}oEhrcJDTj6X>uOn<)`V>$hjpHP4za{G~-?#P0l|HhC4Mfmcn6Tfj8KP2&iQR zxHy(6&)~B)@SsC9JTPnTK~nT{BVW=RqgYbF3Lzp6A_w7zG=xES2+}@d6gIQc=j#Z$ zVhXjDO(x#ciYDVVW%zn-Dq?hXn9oZsSS0cRB;$-%9-~AC+Ilz&KvsGvIsaa%2-Yg% zG%J@Hueaqt%kLF-ft(eqT~m=c33IGP#ruZdNZ8?)(q{BYw+Np&miaC#wBUS@1uR5j z*K*_fOdDXI-vzUyaHavlgc?D)M!GPD$kdTehxK3!5F2cIR^j2 zF?0MPSB@)UD$4FP!a9@eL$O? zaCHgpm++i2jC0-x));W{1{DV2sHn7qqtOoDL}iaQr_k2rn@AX;qM!sLMf#IYISihx z5Gl4^@Q*Ew^+E+n#|BZH+Q;KGTBNN*Ia5>XF#_vqO&x7cQ7qspyTV$B*sUZ>SpAc# z5!>k`Z*DT1@0ra)jm#=~&&7neQm$wxH%uatsSH)B5Evn(``~+3D8zxiPo2?fAh@z9 z+fswahAgM*;ywVl^buE+T>wB)8iS-b%xEo!)!$}mC}&srTMdgDo0JhP*0LCI(JjR{ z*ClN_OcsOciDs+K5jYvm{%b3w2}F~Twbmvy1F1y$strP=PcVKio1{R$aV98pcle0%N?_ zAhtaF>qxQ_XFmB~ROlX8)AQug^u!9rX!NY}k`OcZ?0v=2XIF#(Ww4|z9rCBxZ9*AO zHb;_e4_Z2Y1ZA?Hr<^G2-02T|L|<6cxz~9+^GZ)=PIKno^30);*7@FEy-j!4oe(;u z`d{6zPfaD{V<*{cz9pZ~g?q9Y9sL$`^kyw282CR*FHu-@xuloo`Le(u$T9&u4=qg` z@s?+{L|}iKfQfdQ5&0w5OE4fDh7Dp!K0q^WRs6(>I5CNak*kakF_%$13hfG|Dl?dX zQ_;>R<6IMtL_4oH`dOZugBOluMI_o*UuMkAW%{!)JCSY`gKwH7f=gjqjsag3>Dz4j zQj)1?;tH#9?yME~YE2^Zg$NG&Fw!-QywWx=UzeB8LLk|ZYs*}G8*S?-!=J|5H_nwA zE|y$jr_4~YJD<3mC1b_H3%aj65gQL?Fv*jFYrJMb=gA);!zw|?GRm;N|6%)Zv_FEB z$9*S8&Yp&m$bHRi7#;~HO$=g=C$Ut-(oVK5md^Grg_Ho6Eu75Im}!f8&Lzl9j}}f;Xv`R#wA*6sr_&m|?}O%~ORM!_{7A6x zce|$sAt_4EzTl(4M(9{ftm_f2>G#%%#bX!dfv?Ka zGrD5IkoGS+ummpW6B&5iqJFB4v~?>OAv3UAlE}`&T58`?d;|BPH0U-CL%oLPoexC@ zZv)fyO~PR@ef7ANhX!$0LMSAI6g_Z>isX?R7WdHqt?_s=>hV(ER0-DbTapK+wnqXh z;7c-GXiKi`xChaqB@s*#Ct;c@rs{S{)C@9gtfmf)|Ke(@)rK%>jU{Rb$XKZyeJueIIu`{!Y zDD*>Ew?o{)8eaBhaIRVQhF}&fq_=5cs$BMI(E2^t_bbpyW&(p|aPTxEpuE*&`*nLz z-tnglFKBU{#2-%s(>vOltpx6ZfR+4o~6$=D=6;?p$y zB%e<*PszTY^Lv7pXl;mM))DJf3%Zg~%qxQCt)zJ~X^twDG+0P!t+HAD#C&U?zmI2* z9J;SVqf_N_IEOykTz@=-xToOtzi9@VN6XQ8g3vcl2jc0y?z?$-y2pKL!4nSy(BIYL zi6;T*rz*V!Qf`(&Q0JO3s$AOIn(Dq^xEOMDgMncshJr27pzDFl%3$BG@xFEcVBfR+ z$9|*d_}>X^Bdsk+(L^?S0dbr{Xib>(M5wa0B95^$wp!ruAEeriS8Zpah#P0pz|(Ne z?Nn*ja-g-+1u+NytVpqEsu9~E^8#nHlB=BBlrsgkvAG01%zE3EZjz;@^|&yVwhL1Y zs;)H&=c$-Pt?8;n{QRzvs%XV1qE1zJ-_Dwvls;CZo0_E35`e7~%u!P?dEKg}Q$?B% zaX=`Iy+nykH8i7*tC_ZSk9QB{bTy;%DwV2*XW`rwbUuLhSnXf%r; z%_;(E{_i1oM^eXc^*heJQnS?T&dYO`qX-c3jR~q@WA`m2T;05D`Bx=f)x3)lHlAg- zmGUe*|G<(kzKzahFVFpEqQKY%KfZ9V{X0A*;UE?@e+(iL*@JPf%r3*dtqaB#g(`*6 z&UndZg*FV0QLP!bS~=CxoPpNb%sH86P8Gm5xf{dAfQr@R&qjf^0mk}%iB{8nA|Wa; zR%^=1f~#6tg{?(ZSjw@t&#H2~T-6FAV(fKmA@*G*_PEh3H?G=UEID+yK5n~dB7-K9 zFfF4a1*77vs)Go0jER8XCV89;*r7VPbz206D;IKqhu`U>vk$BHzo=554Re~NOZ#wY zVIS5e%%Z!`#+(%}ZZkq5-E2^wTMHB6t!ge&pF1Y%J}}Bk`Y)=2c|+ZK`YWJLmb#F_c$ihmM!;Z!Ei-+XprUAo=F9U_K4WwZcj+2gc8`s<#rW zdQAv(HEtzVT)GD%@vf6Igt$MrdIdEPeIRB@MLo2v;DZs_AWdBZ#xEuBN7kC)fg)nLQm0RL^V1pX=jybe`F*hu@h! zHRQ!f>6un!>i`U=Q_hi~&=I825o1ya#@C?GQF}_EBh--;*+-|GV^H?dEc@s&%ZB4z zl)Yw8%RX8i<0_Cp3xgL(OIX~0iAVwicYUuYnd$YKSyw*m1r@t*t?|weK~e z)mK34OjQLDX3**bT4#<#YnwW=7_Ds{v=BD0HCp`^t$r_BI9aX)S_5hTXbo7jz~VT> zlk#kqm1jBtH@+!n2e@QAxn%p8TmpBx$oDKa-*IxucD2KDN$%YMEZ7gvAJ(ydfrMv! zNa&fT11--UNy~H8*~PRx$3shm&1+4|b1f~;_0aO!RZVY1z%MvGE#kmQ@s3LXS<8Ov zyzLA)gKBWF3v-{XDK$tY@Up-VU#P*RH}U;;Oe;({?~OW}LT&72&npuH7&3O1t6I<+ zlMpE^f#_bZAQR6|IYX$9^I09|k69h?+l%TL-qY$hUk!0EM2^-<(W2k{0d@&fy7L}^ z$Z1&T0(F7R0^yG{vhK69Q|;_^E>t^{3#0f3zJmK+M0@DwN$`C54Rt~^{D8WkFh$jk z89PR}vsB&R))DSRRX5H$#<{r^LNNU1^D{E?;!EtV>8|a+wBOP41aN(WH+~ z+NE}N|HEz4&eiHtbz!e_jk>Iux35;$RAX;+Ad;z42%Fau1NXVRWYNaCUR^KCHt9({ z9n@y7FPs(MXdf;>ojW(!Cm7U)960mseg%y(>D-iiic;f7b(6L>LQ@SoH>;aKzMIs| z`t-zG)GaLubxSI{CHEmFzPWHHC!F7!r=`sIz4nQRWWph>c?SU&a&EIvJT%j}9qQkE z)O*#f>Na)R8s+q=+db_)vjmARW9_eC;ydgUPtSDTXPIM@`Iu~k=f?dpYbXt2|veTFysO$7-!ROr;)9J?W#3r~~ z-3=zX+hro!TfCn$J=PCyOOLt`^t@1A$5Jxy393u<0c+~r`b0H2m?pbqYIpZWRSmx% z30f-$5}`n3_D()z!V=f_GDMk~O-r_yRcC^#%E8Vc=OT5{D4xggRwJ%1@;KT_f;8`v zH4=4^_C9uTP+hFOk6k=l%*SRA@-R+ttQ_&z%tOv(9$!s1xHy7KHOwpmi}%6}KODIn zzzlKVSB^fmo6?hOE~jFM8sR#y18T+5Y{S$zT|;WAl`g4veWp;nR82~9-JDI->cW8LhMBW9);-YoPa1EGptaH0dRx){}C1_d6wL7@>7C^(#xGGz3@uzxwxdJvHb6etGF0k(=}0tG9V zW&&lSu70dy$^?qfEsC6XM`P=HnLw!*jk;bYQ0ip@h4(7F`Q zGN5-e>D@dgy?vfg#ycC-tTL9~Bjt*JX=hMm0^ES4;A&*6YA2-aV?s*zC2`g1cFN=A zjCR#wAytiPZ{JwAHK z7_*PAKpem48R#ZU4s?Bs6h)3!YCMis;&imqBdc)0$v4oQ8&q@kKsQ&wsXLmqIOTv- z_eh-PsqSK&=6P^J*u2&_TK zF}VXiSil{N_LMsosD+k0^nQX8V_a}Vk4ru82zNFdw~Szo#j2;6jEg;FMA*F6WZd77 zaeogPiw3wjn9mMym*@d5X7TL+_W&}qu3|lPqX)PL6c2Ds3e|)GZa8-vXb<_q0WOT$ zoCuBKVdjHixF9N*rkrJ{ilwZIrDIlw-zUmA8|qTE%nrfK`1V+Mo(T3|gh>b2&8^i( z&xIs~xkq3X7AIB_CFrBHdwLZt7^Mx6*hZzKCiG;q=!c0^xmFD;v|d=5=JnOTE zf)Uleaky5a$TgE>kq8dq7PBgyy?Lu5S{Gkak(H9mCxh9&s=Nq zpE*Ot^l*}6M(kiOj4feYW1cv;)YAot)-vp1hjJOs%*9*mnhg$Kh__dDg1VYRVPb_7 z=QFij$4=`FVypNlNIu$c6Z z!eWx9w9#Cm(d2@)+^8uB6^cPOSdyl1`s6}$K71sw85qw4CG~^@42rpKT(0w~&^4h4 zKr;TZmS4S2Xtrhs%^51RrcCM(0evRD*sKlFMk*N5Bc-SelsGxLp(>Y$D@kQIO<;0_$ruDs$gMp(8T%yA$MQ44EM1w(wa9b3KgP z!fSEoA+^{RzDU-3u`_(ZE@X*K+$n5<*s2n2^NBIrylYoSj^Qx5$o&E7ghy(Ql%v>$l8cp8Ui>>j=Jfm+oTVmV$^`pX=KTo|kzpPf`qpVt z14m9^${I3d&6rH--$XUej;KZ@$&{ppmM~svlbcbp!|?B(YJyXiNdVgH`_VP7hhF|& zFfBdY>=+r|`d?9I3Y|x8RZ~crDaDjIW2AiWS+I_`xlU1aomXYx3|_i5-9|KOMpnab zR&v1$@GOJ>h2Q5KbN|u<-0nN~Ma01Krs02UbGyXRB(St_ zwoc!5y=%n%4o~Sis|5|AWi%<)l3egGH~qFGJD`T$Q4nD|XEQ!|;cs^PimQoo@A@IA zfH?KRvcA`$hCmUeic!9s_U(5gJzis7+37(ybElwtHtQ1s*%>Ebyi^Ep#>ix57RV z?tvL)jvjAyr7FD#NiTLaIh>vk>4mXyP=)ovSQvZOLxELL8!ASv=gL~_1w5SxepD9I zY5hpj``Yz5>4g0;o!1xvf|-IVFuDM@jV#UQhX{L%4u((=Zvf4#llNOhYa!`d zj&sjfkW~fiB&ZvsGI)LHt%%LT)_%inju~K^w$A;NaNAXGn>z-0QTSz&ejE#PRkc{6 zdPl}K5suPE;TOC${4&wAnhGcM)#R9onqwvf)g;X^ld!GdsfDk`ZOUQ0afoRh{h~ zoGG->h3f|~n1^d!l&O-kJAg72p7Cp;cyCivNrkD!RCvba5?!?BHJ@fbUMfsgd+Aef z>Qhb{JXKEs>Wcs8x7xcn&=Uv1G?a=|9J*9rFjxu7n{8an%s|Gb4h1k-2l zY#;K&lJ!-p58+ZF{lV>xI~5fAgPV%`Mx2mBKW>cahNq?kj#>Y|s*|A|iEc9g2RoUX z-E=Y_FL_}dgm~p>{fv?Ku&K}?Gq~;^+15+d4L6^I`r0KacCXtbU%D?u`EYTv``+HT zJn=Txx3jm;WkCF-yXV$mRV0P9gz#;t=MH|^a~p5LQ~|p)cZs}cTa2AL@Og}*w-vBt zIqoh|^!Z5Lhlvb@>F{JUJVgPb#uOm_S*~#okCmECV{vTmCPKTfVPBs?;VZ}_3&^-` z-c1dsAnt)riuk08@hMTmey={`#3!X{JFk)&)?8|5Z>?Scb%^df3wl8as~MsfG%bet zZ_`Ay8F==@?o*gSl-9HPR+G!FvWNkb(&Gd8x=~TxIpfqo>T`cV?M{3P?QAy#GoP0J z|HPd7TM>FxtUY^}W5Gz@-%vTtZ*{olgoA@Of~Q;B^uJ#4;%r>kG;BYff^{Fdsc;}# z>U-<<<>_Udt;ZtS_^KZ#`f2UV%K)tZH?Sx;iS@6U#^WXnEe3|B@8smNDCN~+XsXAs ziE1d)K2@&B>r}zF@?_Vyaty0EJ^O;qHHOskVDIA*C~HULDd!&f{SmM`pN8(d5qEay z)6kvQ;Z6_2(?r}%v*JekxVC2^bzdxfil|`QN~vJtF=r$YZ=)7=c`oYq=>;9v%qv2f z#k}GaRqM_xA`Io0 zVeYDHsXP5nr(nAxSjrvv1#RaeNvAR8?2VqHkv&CYaZgcVV%T@k(|A>9*eq~cXIQ%j zi01x?g|d;T`D}=|-5P!ewS@^8+_8Qhyv@3W)%Q+L(Mq#!@z{^6OivZ?dnaw5g8esv z2ao#FupN4<>LNqXIh#k6lwb&-wbFPQqD$?A zv6L>%Tbui_Hs_|^sm<4>G43a|nOn(q-RAV1p7ok;z4S(xgcEE_yv*M4bXIVM1H0)2 z53CZ)iX{9$M-Ubhkff7OIrBl~JgJ-?lgj=L+v8Nw@@jrbDla6J7p0sYK(vSuEgBOd z98!fTg2i@QyNu*ro^lQZM9T@$@-ZRuZEhYfd6%mLUGgr|}|*ek)2P^_D?MepP$ z=9F`I4%>E0bj!X&rW_n2m7XDHl+z1#T0?ePGbTH&Rck?*wJu@C+3wb;Ua(W& z-_g0{WL@K!^dJrtI~*_^o~ZzILs;)S9IKp%C!KXE=U4!^jsUJ36TtOqJpf$4rvR=~ z$By9l!;=f{;_~9*$%TFBkAwYFmlb+w?r*MfHl&=5Ic%0HQ3t-Q#LgzQ2|jM(xL5XY z|Kjc#E9o4ca!vpNjwb<*ACmwlsuMwg6Zez=$Ey>l?`_Nt$l}FGDd%Lsa1voSX-pVS zQKtZgQ}z^wlhnz@m9Xeu&_DM)ECAHT?lSx(oh^oLr{z8`MDz{DoULjrK-p?Rar#tW zx&f@V1&D0POu|!KZ#|ZXq;qD<*#<<~ald>zKjF^pDQb(eL2Zc4 ze-G)ZPLthbxwAyZ?Ep~OK5P!((6+eQrQQs?E&7_IsEDmm6_&4|~(iXnGOcE4$3dd!#@I z?*w>7L&$9)OmmAJe=lZx|B5bpQDlSzJR0*^3uUv2ZGps?z zhpTN_26HO)zZp++4Gk-Ns?r@#BdjLMcv|l@o>Yzb)=B+efQLIA*ESpDZ0w}qac!;e zw<6Jc-{zz;p07E|WFOw@s!)?X3vyBD(Kz$1sRHs|P1f^1PEa{|;?L3cy=szZhkAP> z=iJKzuVdU>g}I<4&UQ&vWKO58MDbep{4XKN2&VHJ5u~7zu)BsUOffS`3FR{MOGtww zM2p>#hTQnejK(^z%^hDVcNwH}0xZEz!V_vR2iE;=a224WWquUvYT8wA5yQ++mdwjH zUpZc$p;f`(|;(^NfR_^Hsiz$o*-P6AdZ7hFW0=;4wz;G3}Jq3vRYIvumdH3)Oo zcsgc{aeNJ?q+v&hmM}xN4>QUuF%|7|p2-vJCDU0l6+KHboh4Jzvt%lImP|#@DxQkA z=hJrwq|n(ZrxB!`P14RTCM}(}k0hm_0aU zfeVlyJo)HzdvvdsN+!xPo5f3gF)eyWR7TN)$2bWAC-pZFx3a(@!UEjn3?T!HF~*Eo zbEpO{3=wS&RUYM0FZvireI8Lba5VCAnAh&eIq*+-LqhI%f*Nq+=b309Vw8Si{Gx?g zDk7dedXeULjq#nv=V8E54H$BFBjANwl+|L_)^Hb1|HTbm=OioO#s74=Whm|`+e+xW zTr=>fBjc=rSiJMv%!Fod>tD1A4WoJgGBn_LnQ6ZuZGRcsd${0sb?4}$?FQJCMTP|PHJ~`B| z>Vjt$X0!S^yH{p7)bNwlS1!x04mJEG_0aO{p`nJg*DO9Z+mJai7~VW|2UsB7%+T#_ z2o7~j+%0bC1~^;ZH-q;2H@M zxAuiTGQ0}0siX4uBbcLKn^41nr<8~C$4gKs!d@ZwtUQw}B;wr9e|%x%)oUkofAfg?S)oAj#*L>Lz(6q1u;)8sf|DT+9qf(mapq)KU4@5zK9o zq@;RwyQXm-@(M}#bso(jkfU?0P{RWkoqAY%O{ii1lBFxM1Nu2TJJ&2PSr>WEeuf%e z`uM7i*`=X|n||JVTK1G2p$>+(FPsnvN<$@m<-oC__Zb?Ca_-ikezLWwzq`$()K9k- zeP?S~zvHe&DS_f>$kw}lN0WBG(@!9getI16iHt3LXSqbfVmU=WS#Hr!mSgmj2#3qfMCwi-fSB-RDP>7U(+r=JYv< z_7y(vdIm~ehQw#kJWm_hzS7~ABol~=F*`<`<0#WyM* zOyrvpA8KG!{sbmDLy}B<^ph3&-^;55gC|YQ?}GLpY9LVgy%5alG9KIV*E&KLZG14r z4FC78qn!LtrA$WUA4M<+D&W(3;*SB9RzZ)>5dHZ z{@4E=lgWQrfMituE{ra6XUkX7Yu{|k--Fq-P{T9d+0>q=L#|K*Gsr*2REJ3_HbQ2^ z_9`fb8l+tgv-qqe+4$V4`FtZj2{lCSI5VH0BgGM@{H4q&CaD%Y+OdR?i4;YK(xkDJ zg|8TuzkwP4Ql6y=?<+yJ>iiZ7GK2hcq|ImL`G02Kc>p0}tIq#R2*arSDcQO!-$XHaq?P2$>%E`TSa&;TDEHii=jlwKP1I4 zD*s22E7vL+-1hvDEAx*DQ}6rllMl{Mkz$xZ{!+x|qLNBP3o^R;@0T8ue@Rj^D&LAc zb1zE9eRpp>IzL@rOe{M-%ewn-)eDRfSd3d4&`3O}0W(0E=NUC#Q_~==L?3>@7Gr$bHzgX38n4eMk z->}RhCF#nUuk6VGMnu#J*R47$PnQ#D3dkToff;5c)xRD)cc{HF)NsU%7thTv5PUKN#)I z29jXlNlMXR|9Z^rH?Yath^Lr>3}Yky!3ctBBX;l;Hfc(@nuG^25rPsjG(Nv5w9_VJZU4;azv2pZA698A0J^BYw>YY6UhTyc!YgIBmofMzG_y5gA5M0-FdK9%5uf-SVn7m|`K!v4QM4 zLzsG{4O6Q`6xB)mQIE85s6`@*>Z6UK@`xyEjrik`9DmdkZ31eEHcTDShN&UiF!e(l zrgmt<)D4YlI5FJx8;UiKF`kA@<^Vp0sq9TerR|Y!RaDydN$(+A>=FcX zpAn^k&6k~+sI(_a647RV#3WRyFbJbc`&sFEMWsCp!Q46WifqWCT#-r}msg_FzKr5? ztK?Z!+Er43sI)bR&2`EvtH+9d+am=rD*s6qG)a<(^4cX|i}Lz8X8adHPL$V+g#_%Y z2u_jm`k15=<@FWzqFM(M<(0=I8|Af?ukMkg>{v(=(PZg%HY9yb{!S*@DM>_4-RKsd z{~BMNB+sJm0S2pGiq^SYAQ$cO??h^zBo&o%ncyoL;iJqYEw3ou34>^cj|iBe8Ge^Z z{w=wQdU&C{5{>Y61ar^JEB5;fG*^hO2d<#-BUgxqSSw$NhS)71h=%wXLiT=nF47El zNovsysiWqGB#CH-n*mNpgkt{t{Q7u##a@-wK#4;nL>D|=@MKheAEe67)k&!fl6oQO zi}S~@0FIe_ir=r~Yf=1uN`37g@+^wqJp!I+d$oM^th{0;yx?Nt3QBc?Q=|ayk}^dB z+{UjyBwvXFNc9&yL_-WM=#I!mw848Nsc3@_67N}(RJ6c<=L^}R`Bm-Co)-x~;G&HyEF z6dz9ADsvZN>xt;ATj5Q%jBo#qbVi(eV*kaAIt+~BASZnF<`iAfmLaR{plGlO&Y@94 z(?8@-+u6Jc)L5n@(3=B-eSg&n{#XC~MgRR-|NTk++%LE*UVzpaU_9o1q>FaX+dmXP z!acS3?eFDLyfSMtaAL*6-uJS8%cC_7-Q{3O8~!cgbDcz{4|N#H?QQT9T8cMpPi>LD z;x5~M%>&eImLnN2smF(E{E^{Jl{JxV$UDPgZBJ!JOIxh$Ma--ac)^Wm(TaE4-I@o+z%Ny3O+v= z`M&S_h%3~5)AtJ5_CaLhRo|1IC^{uKko2K#y5Vo>q)#wP10Wu5@bx$Jo3G<9lb~wQ z^jj(h+}O!;M}H4V!e3ecT&Q~55Vfp-sVCg)318p|-{c8nTF3qN8c+CRo;2U^girIN zdD;{InkPKrkV2kQJmFSPc=5zSn%S_}u<+j%FNDu^!@;tq-x2r5U~Q`D0a1^H28)4z z4b~+lhK9e1jXl};#L)J4qfuqb!{Rwk9unqd%)Totyo)rUBZ{2v&6u=(+w!dsHa$ad z_tXEurYZ>2X0v9M2>crGCjozO;BkPFh)A86M?|(_MXDjDy#sfoVoB{`{SwN>H{C-7 zQoH_vh|02-xuKSQ28JgDB4yFg@ZC@*;q4oC0`T_D2Y4UmUo;*m3lG#Hx({;+k$Cdf z=4kc66A31U zTM!OQ;!Gsc_bC z1pEGsJ1RPr)Esncfh}-U0a(Vc1s^;lek53{KM0l$;oN~xFf{Z5{)LC`<=@26&Y)&T#9q)vi^jEtlkPS_mT+=oG;;r&n`SvpOq4?{>@w`IFa(}&?zq)}+S`?4@D zC1g^5AI4wq!@v%-ANywDLhr*aK3oxMxIJ-1plc!)L{@?s1g+u)3J3n@r$PbaFVt|r z?U#k9=%N0p@v=C)*YBdQO7;`hm}2CS_}4vYcSB~vmN>8A!Zx+ zfsMGl3K7iKMhx*2W@RIm@)H(lBaUDMA+!B&7NwAu^`8d{1|=%CoX^rW#;PV?3B!aA3#obo^n=PJN{u8dXyRo;|vO#HE?0#Z5y!qMlJx+^D8$cosZucYJtR z|0cI*vptn1NZ^Zs4gCHv zLDM#{CC)bk1TF(?{OONbAClPy z{hU1{b6lul!f?fsEZ#iwr`W12rc#!CWcvA8C$lWrKS0LfYs*JNjNp44F{vIA{LnI% z5Jywus`;XF$}~w~J}5-_FF#+&(@n{fGxaiPSOclsmt}p{2K!p29)+ky)%mWneybwzKo^(%L1Ayl>gzDIO5YFXJ$f@N}O;3I*$if|L8m( zK7;iAeu#q*V8=v*AsJUpLhu*TzWy1B`z4hpIj} z$jpSJ^}FC8vu#O5`X6JOpZ#U)@%qRc^Qm61UoDhleKK#>7BeGr@PjUBxB|a`D9k}@ z1a5hfC0xSevPv7NKpY~03*Ee#CwWE6IM$SLV5Zmb2x^L@Wt(-4buA9|kIfjw&NScx z>X=L;GzCcnQbw9TStc~FS|({`!z8N?qk=n`*sb8zMHSq%fG>~tRN_Zb5G(OxEU>Qs zN#vwyN}*jR?UzYfdXi|i3{_-WV&?R7cwP=x(j{?Op|f>E@xD(}Z;ERDuk+i%K^D=n zQa0u?2-n%2Dh%c9pV&{koq1sgJ8Xb){)n4N&<(*|wB)BkMuY2oJP2lt3}7NEA`L?7 zD;(g&Df%_&Vohu&8t9hg;9LQ}Jku486pE~%TeCuWF}J*+E-qFkc}Mga7oA@NOW_; z27G_A(C#$S>Y#qHa3UhCn56i$VOM0D%4<2nn_tIK9z!efEW)zjq-%N!l(5fCA4_oMxlweu7DG3261OM&P zp)5j6F$44tN{H%abRly9o0TvJxnV0|Hh2aV9|v$4RD6Oka&3bS&Ze>NFyoU1#VLg`3s?$<#ll9>|^VyH#w2Y~0H2&aVi}p}-9&t3l)|DGgoZLg3SI-HI@bS&6Vm z>F|gu9EqjNt;{r7Ze!Vwa2rme@rWqPRX>gN@0=U#nvz)GY3n^;=Mz0+XJN_sZE z7y7W3ROf}{O;>^@j(qD%BW;ohJtQ1B)a3`}n{CkFkS~Zswmw5GM-1ak4o13>Xq0tY z10tqw?)$Uy$!m|hc@x5RqNe98)A~GDg-cU%jBlv>^{m7vr+k*kG{A7~1O@1;nEk7CZ%TLCQTYhppP$WM+%tN$|Fzf#^kZg#* zcoBA&k_s})*YEsdmEgRMVwFVRy;$X2mmiH-6}ft*D-T5WK^};F1=sO8Tb%(P5}hno zp@lD7GC3=0O{PZD5+61IJrP0V3(bKPa*KE6?l0Z4Spw&kSay4@kx4Mw=ChdJc;^}K=9*NBF ziY2nvVc!;+jB^XTGWmtw%Ve_XTWIzr)TIHI;a8U)1y+>LpXYya=nEu4UH=c**ikp^ zyQ$|)_v$%Js@o|;{&vbZ(@v@R{&#xL)hKI*SI_aZQ$kw0EwV^YTm1j1`w}p@imL6- z?VjFdNjg30o=ie!l914J_jHzF>EtGX5D5Fem|Hv1UCfZ|GuZH?ycKBGfeXFf8YPl^Gx4+&#mRusZ-~iI(4cp zt9=I(=P8c!jps~RI-H3^8jL&^`PLf@_E_|792t8Ui>#o8@gV$c**LlgJ@SaXKi1ev zuXt~)eFAjhpVBj&&q*Ce>pG6}^v_AUwj=8F&;DZ>I}Q4Wh?=27G%-eq-^gQx8G>V^ z&blDsN8{db?oIkgr#byY$VE0ImwD>0MNM1N3Csl+7 zv-LemJ_Z3vpJpT!0VdlK*WA8nOwy?lS2z}UqGn56aBTvPOYmRAe;LSyVm`jD(ds|Y z?Cv*SJa`{rWMlKd{yn|>>kqxTUqV63pOK7S+tLv`C)v&99Ct8sUX}V>NzWt|yl`yH zZz)_8>VZv7N9~f>!=|JSlTp&&HqAOa)nrX&EtfkysPw|?^!X2A%h*}>93xAF_|lXk z9UByzpjk9jMN{~vq@itNh<|AZ+D9y?WNeIuTO}ujtvt@#UpOyZ&4N6QM!9e+wX@Bw zaAtglxxCqv@GI2vy&xO-_C8#|x7(vc&-akC`IhOFPUc;`P9{w2UyBCDf*OnBnu#QXuT*&xbW*KpiK>^;)2%c_`KNU&^tP1c>r||) z3AfBwrF*=B)wG5+l6Rid5hbHjiIA^Mg>PVRN)zA$V9KB}#jKB1G76M5Y-BL;mQft~ zPCJOh>&ag0IOC&m)HA*xp#~Uz|3x%9s|TYv@i!r)UXdi^P1Kn(I8i60aiUJBny5Q9 zoGnn7hmgiRgzCvX<=h8Qw_118zAkIsjm2AnK-Y0tw0b0&=bAtCo~uAsnrOAccb`1_ z8XQW%mgcVY8LN6J-`&2c{XnPoW0uWjT_a@R*M9cb>^(dIs|au{?^4eRx|g=3)J>+t|;=LG0Mxi$^_gxAaf z;)ppWzJlsT8WT(z920~z@;9MoOgz+(hmgiRgj!>Q8>$0i;*6!>+pj+L^QBIW4KRB2 zcA(pR>h`sqQ)7Er{%#14{;EyX^p&nOkls~RW26_5B zM;Qqw`NRcCs^Uqk$(%Rl{;1Fdp2UJNN5qe@G{o2C@~r~NFqAJdDb(GoU{>BQPlBA#AWVScoW_H?7IBz_3r|83byQpKLycei!660tH2 zOPm^clhyY^hcIQ3H(@K;%Niu)2>c~&ONO?=m49`X6FuZ@@wP z`ayrtiY{y^v~#7E>8Pz%3#)m9RjE{r)uzzh!p%k@%|&#kjMb$Pp;_tuTDKlqRu&^Y z05lH8X_=3RN1${{0d9OT6S06G2JAwpAZ@Z56jUGz=|F^(aZ94{Nl6!ySKf(n%Z`wz z7pZpHb@C*pE;$iTmf97$6QLIdf7;fCea(KS-esHI>*N(d?hkQWMi7S4!}Rc|M#+kU zwulcZUoUW335JK3jn|g57{^P?jIz{ChpiFN!bcX?ZIu=#y^J&?wV-fGCR|7#^M=#` zh-ATq^r(}1->YMYWMP6+W7&{@Qc_S3ijF=Ct>Ir#Cfd%}vDn)ja7JHqfQ}$f8}#)r zhttQA$nR}LehhT#U zHMr5(PoidO?5E_~H1M=sqXW;#H8HSBuE~LCaRp<(C5%aVx))>+^pzj)!;*qpkB~;K zN2t-1tSy*_kj6ZOnmoiTn1_(YJcOD&Y=w#&<=uZfZj{6*O57;#;O={u;zqxLZI?w~ z{|EdyB-=>(nw@9q>-up^>eaS<@QynK2F0qgKb!Q7?JObfSDd_`QXwCY8kW^)~g zs^pi>db&&i?y$kQ1)L$BPO3VJYwfz`9b*%r8E5sXw>YqO7LJN;vBDDDQY~nIOR725 zX15=Q>JQq2g~IUZ;5s<-&o>@1u-J;ADTVrl~F z#0k}@iFTb6QYpXA(^%)XJ1^TgHQo^2qfjcTK|zlOS<#cd+WZpx%lBy{zW(tA^l6Z_ zO2(&w7<|HUfFOJt>}MV+c--W#?amcrF}3K2(*m9iMkV@*-78OGsgh_Cy3yw*MqE)11#lxIO5wxJ+?j`n5{d+Hd` zDDY-TYmT5dLyW2&>g=eax6+$IV!+fP6>wqmabdf0VS|kZVsjfc5F5uCBU1)75E{m{ zOy8gJL2!eycfMuab7&ekWrn%0Kv$T1-%r_~zlI6T_#-t{eQ3sWz>u2id0sOEFR(eC z)nB7RnL~0zTaT@em`>SAyc4Fj;!g?Fb`sfUjB7wn?M0S~@2(*BlegITaH7BQVF@<$ zn2isqDECLi3JWR5tZ|0`d5pW5ze1X7SRzg7oeGqaYB1j6Z*D~MK4!Upc{gWq@=HVmt_Y_R%qMI5YgMI7E?TD8IL>c{ATOoaivccReZ%F z%eI)kuxW;mNWw-w8fhQH<^UgQ&*E>Gs<%YykV)y`Fm7Ca{hspVW;m%To2>d6x zHVwQi*XY2XiA`tq6Wba&cpgetYneKZi8;AR){6Bc$Qa_ z_k8O#oynjYw|F5Rn}_sb?JHy_b%uoR2FC>SYAA z?7?M$eqXazJ}C`M{QP%p&v1M|oxnd;1Lp`*H+G<~B|J)8Vn>fC9H~bd{4~VD8)wVL zGS0k)Lp2ghT!Q=<^{R!D?e?|GTRW-%cGU@kfgdx12Kk1iUS7y?$ zwv(Z5b(2n5bvNtZicy(xk}J5Us#lt@Ko zJEb)TFC+j|%;cN>-?#8Ek%?aijxnF~+=t6riziyhq~rKvk&R6fo`58_4@eO zwmHMq5l=N+Sfud1A?*pUs!r+nv;r2Kysbr6bw1HAzkPXZw;7kfy;85weTe8I8z2sZke(-mm^5V<=GjJJt4dwN3%e%ku zZZ_{;=Ut9>Z{SXLrFG#6Qsn29A~O(*ImG750o$`#Rat`VI z8b6?#&ock;LZd&(64rGNmn0Bh-iv+;vi5H0T$h154*VFcD?aHC{0FLG2mVv8 zO#^ST+Rkd?FmxpNj5^c!61;9@o?y2U(%7wpn)xNm4dx-FF%O}d6<9aVO5vZ1O`m0V zKTEf|e^xaMoV%wVM`gm+~teoKDa(b${=Co%%Jiy@s*H-Z>tasKRa` zDR3cjb?k&4G`_GzR5{3J46@;;nIjotyALNB{)Gz3SZ}f7&gy%y#4Gm2F#N=%LEa#w zkv9l6{KR5|c?fCDL#XDb&BHJei?okVOjqI#n9yef z7Fo{*GB@+(2(#OAgwOA~Pk~sW=nPceeVdV(UeQuN^Z=)J^_rK-ZG_aY~ zc2>6;O^z^6un!4o>_b9Lzq8z69zq)P5ITw+DNt6#;@{lUsj+mcn*(x$tqI7H9hvXx z<`dxvVGUNkk>m&~;Q?>V&v6trr89zF;MRe%_z`{J>9M+|8*mtKH9Vc!~VZz$QilUkosz-={1x z*o#5Cnt3S$q%8Oq`TVZS0HTCWBQABF1{uIMHg=kCHIIBg%Bny|X-C*N)Wwc+Sf_6T z@-f&$gf#XLp}L1Ut4Fh!FEa-+Oy;3co*$V{(?~ z+YMLQ_zM;I~&Tp&UJDvm}2$hoDDoCtEbyh?>qGS3C@N&gvXr?&>`bgsX0vUdB<<$ zg(dch=!6w6ZQ=FA$%#~cjAYmkD|oer*Yg^w+}=Wq-oUV?~X$j}B)m9&_+@43$`P?##evO1aiTZcI97*GV+60HnLlz2$TC?q>LZ|;e zVAEXaidCCw2fT5Cvv+$g3wp?ep(PqueG;qU9MsdhPy5pH4*i;LyPgNUhQz|Tv#fT+ zF{lRB^Nxa|;Mk}8OyfA-(5b2NfQ2Q07#}^5ZE0UYT z@-gJU2QP$>Agp)=pgS5tch-T>-t^p9=pI)$6&i9r@Dz~6= zeEre^CdC=xMm*+f_-T$Qrf!L8hQblA(OAA8V22?mL#t_^jU{?O9EV_$G?}phoZz#P!KyIGRyL>@g%$V;QN9NBLb!UWWuOdL_r7urS8r>#6r~&`Z%DkvBXRCS zq_5EHP=zw~pAN)OC!$~CytEG_)FsJFHk)bhe;j!RniaH#m&`ybFY1)vHuYjWFOh)> zxa2YZ!l6&0{4&R1<&(%C;47!If=@|Cqp|)1;z?QxM}-`9XlvzKfLjwdKs2FM{@__K z*xD=i1K#ucrrz9UWT=ENCet{!eZw0VKE+NfFz^OP@L@zgjFhwSSpr-N&gRZdI1Tq| z+!wEP;+3D^X8oH;Iy;^Zam;d+EVqBCH-#~cuLQH@bzc`2V#o`{XHE!C5jpoevnCnN z_F+pT%ELcPn}+`){tG)fHIgN_7T6{WIsJ5+Jay`)b@GH26iJDJh4TiY142y!srtep z=;=_HbgZ#|a2lzc=;PGbX;I~IzZWRfE&-vu<%h0$eS2W23TBi@v+)lRCjSt(p~9lr z2B;3Fb`f&T<{77iKyx^?4|!U8cR5zR1TIYFL$iH8 zuF#b@TO3qdK)F+lhxO^f*`z8)HPuhwtom>vmeN8_to9+`qxexVV)-D#4Sf_FOU3o& z2#s3ea-2hJ!q5m(F?0z28BDlY|HF}tkMZCq?vsfNFdP(h93taHUTC&f44;Z%>) zqhjPT_btYK3iG_kEhwxhYZq2HKJNDn<6yU-q-aJBH4C>w+EF+U$mq_h(JjNK2a%wv zBNg-ceNsFG6d(STkhyXV2HV1u@y+{aal27d$`E%KA!V=+WBj_hRfEkyASQL!9=IlG z)2Ol0mn`g{veXZZaPDS&e4;oU=Vtqo(afA5^OGY|=3%%l^v4^@TsC}}da!k+d#A-V zAa2vCozH>NkT@SzRp7u+;>=yD)_Cqqmgx;^$TjkKHj>7SXI={!tNwT{lbQ(0NFH`g|~dZ_J%58`EwNu)yq7uP^BM=u~_al3P$CuAyGZ*e$cvqKkqpSctN#SVIk^g&ZtL}9p$v(JV1$Gg2X$K}Dc@xe2u;81eUZ6fcPg{dC3>sYG|)(5Tv3^f zNYr$+orh&hpJF>ei`~YFXzL987v`p-ej^cAu^pbhX5u-EXRo1nj#Y0O7doB60VCJT zAOXZ*@a&|n`MncUmiC*SO(zOr2_hIde=?J9M!10F??wKkj{)*GGk-Gd` zG6S--n$upn94ZL5pxA#?s+YoUR`lv0!VByBH}BXf3-_RQ8o8}Xg>^I07;3{=>m#;W zTPN(OJ?5fkXq>d*pGMpi{I9`3^~J@oo~TKl8|I(y{sH)z;G}Qi|Cjh*Nf@qtSO32Z zFut#ckM_*;XVm2}W!Ts9`AFncJ_~~9olNEPa;gV9_b1I+0Lv9*e z1y(m{gWhzenmMTV?SqU_Xm9VIq)NRWmvn2in24oXE1w}O)rxP7eVyPo0k>&zTi)Z2 z!TnK2;7Ep zet`Evi*FHS=iQ)1+>)63UL>Zz7pdx0zCz5gr2zCwY0Oi={AyzU0b=D;Ze)^)l4PPz zG7(9xVUqK#Bxy;K)=APxVi`o0%TS81GG0+{Dx!0`Ab@j(3NwknJ(imrEeq+mr1W$ei}}?5y~)#qymb z9VaW3+<5hbj?n2*aA_5jfdQE&ORnYu=6-yv-3da{*Cno|n9`Rmp_egmcA=hE^Jixe zfn#tSqj4}XIn4&Y{%u5l<<~fd6EZFBt#RoJ&YzWR9p8%(Jxkr12kP5&(mauWQpx7l z!j$TchmRyk6LgH_4x*!3 zl3+zQkHLzbehHeK)w8I2A;<};QSMdm#l74m)bq04^s^-qZN=qNDrH*FdMrZt-&2DX z0DVmdgLoORf#}0*aooX{vE+uBeL;NfRFZ{)#&W&In)_E7%=6E zEAHV`J_@u1ZE( zE*6u|fHma2fh8LMU36Sq`?&S=-_hq7g@v+oDg%&*BY@Xurk+~dRYBitS8-0d(p=S$dHP&wRnRTm74fNgt{#b&_qZ?kdoqPii zdXKGJ5-a=(Iu-@7z6FzsZWI}#rGj~)Xzxw%>q4nWve0h8rxQFS))P)ept9l+ zq6vIThN?@2z+VXuPZqPF>GABmDb-m=z)Bom4aiW2?HLaJ0r)QW@Ga~cAjo28keFx* zJyE`PB#_D#=;&l&8)uLx>G#=K57?Ffd97th@$2GtdS|3KQRxH9Qi89^s;2=aI!$V} zUcAq|7;Ty-nY5{MJH1rWPq7f}?a-bU|LXzRZVv#A#gmlKI*^T0OrV+kHbB7ep3yh( zT>!?+_1|M+&Go<75{Y)Z)bjs~L{qZl%b@9A7A>`{KpVLKD|S$Usm2bts)>%e?mf@2lR& zVvw08ja6kmf-I(kpWYI&_AoQXD_^B#mDyWhK)yb_rfU>y@v#^@w2eOw=j1*X9gHSI z-(`Zt__5cM&#))k`SrfRV@Vq7%i@G=E4qgBYB?ON&|F2%r`pNcD6JAo5g%u;hb0aCHv7SnjM1kE|Yg1dle z%<3j}a-oH7GKSU7$54CaI`+)txO?U(h&~Gy%uf|6r=^+|DlHCS}QoR70*E5gYavK9a(4r7M($N)~HC`+R!xBmVnxS<|5>>n$wnQ zGtFsJ%^B~c#*f&X)_1Kro6r+N;L*fSytrM#<6~uE_Ds@kPJz|qHU4H-%zKLSksZ*q z(2k+f387EY*5-+*3r`%kueyI?T=vy*m#F(sINRUlT*P~>zdVmz`*KW=JgfC~Wp;JW z&BwaN_w7NqFzZ{1e{LM7EBH+O$GSS*kK!s{VT*OmcFz}JLFG6Z4@_-e3n%NPmWJgO zdkF@1iscvNlkWoL(!?`i3sIF)Y<~vVdImT*hG+vkUgLvg@r~qo z9`h>k!3jh~C}-}ZIxR6(zm+6rr8UFlL!tZTc{F@D zUFG0ZQ$LU1$^>0#YlaqU;hU!J;Q+Q+a_>;H~>Nuk}p9O(t z$FxGy;sm8Xiqhr$x0oCsWJ|v%Sgt@J^`6ECcfIy)sczi+=F4Oj7~?Swm6$C-INa#Sqv3 z3hkNZLLu8+*si0wFmp0Oemk4tExQo*y6VPyx0Iu;FI(QDr~iPS_VivIkLW4!bL5d> zS)?^lPo#vCs3T3h#`9`)U>Np^lSkylrt%`N`0(5UpYZUEAq%VvzKiy&wN}xKnI-&) zT3ilc!q#ycr5D-Z@>L)C{88F&yxtDYAe?lvu-xAX{SZorM_KKJZWh@h8bka0trf7%+%DBA!%TVpfmRc~t~5i30rP7iGhT_th4cKzSp z+WMJsZ1txa>mTyk|DCO^pBcwe|KLd=i*Ej>oxzj+rw?;~MLe|8!{xTE#f2Higv)#V z+JVbz|LMcd;3?kI!gJ_+A6(-DRs%rC74h|H_uZ||Ga5>(u9ke^ae**1J^qz!vB$dc%(&|u-v%yon6g@EW{gxDuw#u?~cY<5JOi4RNAMogD+HN|8jW8LtJLD_r{S;1$BE{CCX*p|rjmbjUf%|J64LV6kg zAH@F~_>Xt#2^f6Mp%3C8?Ud`;xRwz|ffH9G+)d_S`ko8!v-SND?;Ut5xE5IX0nv>4 z^Rd!=(&ld)kV$2rpa}q(Yh^u@D76k?jbcu8!oUY+IU#X6q8P% ze$=XGH#|90D9AiN^zw|RCS@$oK&1cSK2P*N1vVLVDLSshD|WpfUOOuyr`3wc!BI(m zbCw3P(Nm=g1A{{Gp zasdo4X3OBbj-)%x6I{5F^knk{XF??1iJB&-I#(<@kGUo{U_T`_g;0$>?-ce^J?y8X zrVfMs)RD38V1=m&*r{^2k35k8o8g$~UFKsd0JI{N$(S+D=?hSEo76V?g+Z!4Jq zo7A*naG5p&F5Yleld>y8f%bG)Cf%*3X6fmLGg8x2-HMlFPBkOd4Ib-G&G6?`QJGEk z77=lJ6A0RacvE{KM3{b+nxSV~Q&L?$tyZFRSE|drOm(NGt7WbT$_ktiB-m?*20`PhDb{~0z)-7Q-?rtSj&OZg>jR* z;I`#X8%)Y0IM}y2o|ymBXe=y#)}|F|Lzeggrh?UHfnS;yL~z$69o4h13O^{~oG6AN zC|R+SQ$2sD(CJM*F)Y%APA^QV-h#*~9d*Q8S+tX%uuS-s$s8%3fW3BmD_d0>qHoz4 zeM_f*TSOrz7_6{fC^nVo^$Ft&$5m&&Cv>{1z#>>!~@TTmE9m@qi#Mjxx9 zo}xgsC$3tGPQXI@Nx(Fa;cSX&5yYJvDkpD8~YY zVVOY{rh?4U2^{N;v*P`d5&>IK!!4LyD>rMdf}eOifna zwt4GP&_PNV2P!EX4%O79rS#s&vjY0jhwKbUA0VBXc`X*W+bcKYmkXDgmoe3>M#r2u zvMyeTc^ZpY>s0q3nX@DF*7N{{XQXB*3eQj!o*9RBnrTsZ=ExMbn>i(u-nOteP~BFI z%O%)}oZ1#`-L}2|5)7L3tkf(!UUQbRW@p0^n5}5zq-Ob%o2hM69ZF+;%~))yEV{|w zcqs@glRBB20@?9b&)@dE3{LX?`!M-Y>{lBL`}G8fQ%aQ-aY~9fpmk4bjzyd~BNNB4 z-*z^Ya@q9ug%gA9HdnLT+&XrfQ;LJp=Hg8E?Ni%>klUwnskxesrUTLk$o83e84H~B z?>+WtLZZ2sG9`H97Sdq5k$GcYp!xGt^HuZbtLFE{Q}a^2R`YvDX?{-kdp?DjVJ{*# zl~?_akAmcVXne$;O&3$eS)xF*E7Vj#YnxONg0_H8U*<{VSG-QA+38e&r#hS}!4KVe zN71`OfCM|Hc2p$TQITM$cxs2#P8JDv8ifRUgzj?Sn;4ue#qFJHPan(_HEtU?+3J;4 zvbAu0lP2KKDI9U(6L4oCV5^hbIRIq9U{A@Zz85v%6$7(yVVst)C#4BiHEP8-F1B1a zvpKvXE_P42C!8sa>*#B1@@)~!t<}^ECU!togVO1_<3n*s`4#?rvaPAu0kdPci68cb zw?&je(i~Iv2rA5?!j?#UMQaltItt?^_aR0t6Skc@LbRc@Hu_n_$w( z{0v`;Nu^dmGfzm&pO1-C)7)YfJIUrEkvBzRetF;w$$Q`gX*OoBExRnI%#Mi}Mw%ql z%r1>dSa80&ly5w4^~;9IN+8bPpC9Mm zZ<%4@6+l3=u0NKMaCvSF@H3kK&mI{oT=tXI%gXg3EQklPfJZr z?D9)?_q0^gVlock$$3Xw#D-c&B0UeqOFa*rju3Cn?wC3Cz=lsH%`}?#sp$>$G&-y^ z3yp^AVRLg5%~=@hOHGyDm0;CWmrV3h+o;KV+!zO`X;^UE4lS`bNIxhJ(hrJ*Fu&jB zTFz;csirvt?dcg#45}QxAJLd5ahsYUh^CT}zW55oUo%rPF*#d-wyNrzyjE}Pw|Zu3 z+hJP0?Fg+VTc#2L)?vJH7buRUxRm3B+u_U&n$9@aNc*?MU)qGOU&*wx*p`v)k8(~% z@u2pkntULr^8rw_m{99z3<0e^0BNV*T4GIhbHbwfb;U&d#)!v3e6q6lWJ4 zhR&Vkq1yABGq}!sDn=1CJWL8d61V4tz@CC%YPvCoDzkZ9VpVZUwP|IbPpp}7pyJo< z0NwUN72CtuJ_gt55hhzTWWN^`yM(HjAOgjBl7?*_2TTg%*iPwF;^yrxcoPKP zVl-rVb@XLq@;hSPLPw3zU!wKzf>uvjch=%UbmLH*F*b>6&$il>~!&fb6?>N7|!AyRle^z%XE3) z0K8{nMJE7C$$? zcVX1x#qn;6@4%)LD>c@n^qY8MmkBfpV&Z8JD}IfHlAs^fwKe?~{2L^Rh+q#KC&tf^ zkgl!e2arI2gk-&tDWZ1e3`Z)41j+{vNh*GNP`;&TVHY5kmm0mGNOi21epV0kf<%(= zS=|Htx}ki6a@1m(+A9{D@y(%w31O673PHkf9Q+gQgiV_=ELldaXw8V*V*LQ(@pID* zNL}_NWME8cm$+jYk$MD%qAH_6Wp(of(W1SsDa0X~$mv%$0ci*&#GiT6#O zyFAkU#Fu~8!tbbm^5Maxd%3(K$o&Rx#mbCyugv}~^$O&+V5z74O)y5Jd%GvvBW^36Bi+p9Zp&AnkXJW` zFNrbtr{BIj>aNG?2#5)gdk${PRY@Yf)h)M2-12O*d!E!G9k)H8<^AN<{<|IDId^Z> zfh=&|!mqZuC&yTz^eIRgDo>ZB(y@ONbjTU*cjT4@xR0Rh@>}vew8)*xg1SGmXo}k< zbrR%$jhH-2p$$0Kx!Fc6c3^8BkKzj+V~>dM}5fP1hsoFMnTB=#Q!vvaTg;X&>iDTp$_y zQ;4vjQ(yQ%pW83h6XZ_D+wy^ux1dL*SjZ3K@{5V zJ;dyf_;IQ?z}W02=KVj#)CJpa?<9cZ)Bv6)fP>5c{z?GRHh|8f0N_wJfHDCby#}z7 z0FD6zIEMfZ9Rs+S0LmK!_#y!mzXtG40@!Z`@B#s3R0D_~4FLPl07?W<&>O%Z1dwJ1 zu$BN0GXuDe07_&7xQhUaO9Oa~05YTjM2`W0lEMIXAb)V!2nJnfI7th&LV&V*Z@9C09A_tTuT7Oi~;bYJp<@C0RXB<12{kcQ1=FK0RfcX2C$I;s!9WR^#A~<;0@qg%K@OwGJvlTKm}$1 zcd`u1as&7{Q&3YGz%Q7BT2}+)*dZ)Z(tgxyl^rcCa)-kG0+qHMAIltnFe-*mR`I>{yW z-WgJ$*n7wD)rInk`iGc_-G{Gsg9KvtO=X?R#1iB0b!nH_d0!&ZdrEGx1Azm_ElUyO z?`cUYcHaV4G*yy{@t5>+x#L;NIC%~je|t%8G5&r*ox4e%#rWg)*@z597t!LHgWT(w z3gPy6rhL_GB*xzb!bW2J0m*>z_mc1(LGGc*TYjGuAjaR7LMXBLZfUyGm{`=pJY8k4O@+>QupE z&^;}w#Gred?K&B$oTkjxvb|lroY-mbGdR?=l$4|D%lqh5P1&p0--`^gZ56ZV%+<1B zp?AXJ@_?U{ge>J{px@>iJE&>$8C|4E-O{G6lS-9qgoD7}Eh0*X*=X%ch ziORz)UPa5ZHVu{!Bj~_;fW=#l)H365>d@E8S4*;{PW?RPYH>SCtg%$$WuvaRH05SA=xlLWTX9-W- zI)w{&aN%-`x^RI4Pv24(%%u2qle+LYP(1O_QCz-;iy`@q3WQmS0eKzxRrTt6b>X?3 z$g7SZiuUPl^ zOpFJgYTfT{-5+b+pJUzc3{%ULd9?+9V8jn`1iy9i1d$dzi@*^+q7-7S6Zi-UTZ!dZ zEcg#F_!*zoV7N8DeZo*{d?bR=(eQyrLZ>1hW6WjE$mGg4XL;I-gkq;7IDKj!yvq~D zg)?*JW)Op2a1tX1oio@x+fop(xqQzhJpbphcvyW{N*zHi_?}0i;;*{PZdeHs54f6xbLQgy4^Vp5u-s(Ooc40bg9&ZG(U2V~~0 z;tKOY`0jZsf)ir75o5)i8R*k!pXx-M>4}{KG0igAIk0(LI?5$!ECCneT>Z$l7S!2g zO$aP%!q%b{2nA@0%)haGC8(B3$IBPs3OM4&cw%=XGvBTtnM$I9WS|0dc6TaKTo%vv zwcspn$=!mJ(o-!4vDQ>8^0oT;N+}(+*@BvIyme;I&r>)~7P$4brce$-wBwP*>*3x9 z3UgvON;3=G<3PwJ)cuWgRJ#Jy9s(S&M$%ck4lqudLdT+o{EKuycEE?1^V>k|Q+^wW z&!t>pVWj)7*<1E3F5CCQ{fXfpA5J@w$^oBd;g`Q-?bxL4JZkaQtC`h7h^bGwz@-^Y8_X@Y@4j^Ctodl|DYV7 zQkw3`pr*sOp(vw_8v22C-ve43_|i6WkMHm4Z$nk2sA0Yl^*c{5ts)`u=VS-EK!^x- z4}BjsOmM=uTZIE;CyaxR+6J$nqhJH`(@-6}Od6Q1p750)0w<|UMqQ@z(!UI%b^tEc zNPZ(yj49y~oYc>v5ZTNNJ}R%D`_wbn_csDx>_1K^X_;92!dD^bF6CIg+Qrx3^TYjZ3!=YA)DGO395aiL=6Pl8zG&m=zEaTp1Y zKZSe8oV}eIY1Fe+0kXYqXgnziVhBO~#yBy;l zGz+D0g2Z1z5FFWDbzz?=aAA+c2FzNW+C3~NrXPO52c7^9PJ9+J0`Wc1c?CPExFU1) zDT)uNEEZQm2_Xh0jE($%-_pKS=&N2O&xAw2aYCUJ(RWsCR3;hj8HcZV(%5RA~jV^07yrYkW;mC8Az)A4FYlf+}t5bzQg^3n!>DQN89;dR=aUDsbVk z?pK4a+mM@}_R0j6{rTW~mQGM*yn6kM#@7T@-cqGM)A*X8ilcHr-T0cI3XXvyZalTT z4t0Vm|51g%)cBg9%D>fX#=2O!*)oFKD`8doUxM%1GJ-1b*0J&z8($MtiK*AWX?#sk zrA57dr13RDm47MRxOLsajdc?w42`;ZzG>ZSf-0L;?iYfu*)oEnm8hy5L1^{a396*j z>o*%;6I6MVrB^@Ig*~vsxViU%d)3a{{~)KFV}1_CJbC_mxSr(6s|5@e3D+3fwScx6 z*k2x+%b2Q%V)}b$522D4?i`NSzJ`=VtQNvqcY3^bBc9Z}?6(-+a;K$*$m-dJl3`F%8|#KWnK(oyYTd;E43&j`!@?BZ&+fWcwSg~NwIUyxbk z)M!RPo3<+jMoIVeSwY>HrK8c4H^-KXrpY?6xk7G!HF9A~sL`rExa^nA|XV z5}IT*zY?`?BdZ!7$vSE!giJV#?mI}L8m(2}d{{vNT$BxJ&`;aSSEm%as~I%H3f1s5 z!hSa(6t3OoAw+h!$EzU{52fBBBC&68N2cb&QKoN6BGtEXiyZM$$PriMh+E{Sqpe|y zYfKjzvM7XyC5yZ{0plOtU7Q1`7i!#Fn# zPnB@(Wgx93v>ws)3$Da?=AUY#Q{$NJH5D0gfkuv?#F#`x>KEi0bmLgV7T^Q zc-66aTm`XU_>i+NHzJ`wscJ^8CtUjpi$29sHAU6evSNaezgjV0HQY1M>4A>rCX!?mBlaP6n~HSJgSC@k{ODI<^JWsNe8M~rXL za1GZUMi$}6%)H&uNKXDRf#ZeT%D2to+}9q z*M5m#(<@>BZ0pRD1A&Cf*|W}ik?lQJJ?S$*ID|LQs4MQJX`@t|ZD2HH(4VR1^h%BA zg}>)8Q?$;9YmWmd>E=#<7^#;9Z}_P}sVZv_Lf8?7ZavI&tFL{v#??|}JBS@7wcDq^ zkIO8cP_bKPSm6snR?x^B+@}nuw@d>mTQ0#|Y&H5gQjDp6DsUKxduMnlQ~iL=H9S8! zTzd+?rt7?}ei}eI(>06Z)2j{ivs+>sprwI$LBs@*GQfne1Mm%TUsQ`7;3NVg!!FRM z^q-iLjnWDt<;&%;oDIAH=dyv-o+0L}RYq%e*AIauEU3+IoE{ZV8~&xtov=quniK8I z%uRB;S`)>k$2(Q*;Pb_^SFu%vl%m;GreR)r^Gqqa`dSI`KaP|ApDrYnFyiPZp+Czb@BI^>(?oRqvH+wE9zC zF&9)GJ0CIUX+H3yfla*#BooD@E9s^wX3 z@O4KMGJ<5am@ArC@|1Pc%)A_TtjfK1?aLx5j!iU<`>s~3o~jMK*b|KJJL$x-*2K>z z;%k$UOna`#^?7_5GlPX!-g9rYu49RX8cugx0{@O$RA{IhUe`EQNWE19Sy_n1caaTF znpPKpR%^GIRyg{=rxo^xTJHS%bphp0|6d_lWWnWVWg@fWVqCBcEnkb)beyvc1GGVnks+WO`r-6&Kuc#gp~BMA&2EK z;n|wX)VajWlk_w|S)FVTNaJf`BU1nFvEAR$@Zw~2C~ve)C8z1DKEjK?DOGuM6q6Ws*Hh!nJ>+N`34;j%ePt6pMGw zDow<+c?10EP7U%kGCP|rWMUg`;`4?2Ihsl0P!A}6Am+CM&3hXO487@@1Bg{rGEi{J zjnUw*t8Mo3NO_Gdxyy71O8bu~EznrJ;Q7_6X7RN~LI26Zaa%BFe zDRm?q-}JJIoLhl%C$nDzYqa9K^~n+5@G5GD_E+m!dd|T`EqpX7tZQAcV^X7T;hBv~2>P<%4 zvKsYZ?HBcM{inD}tplN+`%q7o8-i(N$f|B(f#J<3khFeDw`YoFuGz**3CJ2&y^Eh+ z#n^B{?GTHM=~D+DUsTAc>0AfY`4{OvWZqE==5|K9`&KS#D!`>mJUTa@k$4CMNqkmN0 z(UXBYwL{s9>DmQ-sc_vPo{n)oor}X6*q`ehn2s+0jzs?FYv zojM&GS@;*}{&D!GeHXtdwEEW4!TF1y6R1#jZKV58|9UPqcXp)v@Rch}aaa(=t3BK-N*#iBHJl*%jeC+oWF$iifIr!Wg7$UU4%J}615 z`~Pc>`&-l<>1Na1Gm)yiR$hJTj01O>i}p!m9_l>*&4YJxQz9_@7v+|3-5$1Y zC&_hB=z=AzQ#c9JDHKeQ`yCdnHfalI{Xv?}I^1($be8`q&txt}13uYw?`|yR)Z1>} z&AkWj705l0Nq;Iy_kZ%lW$y2#QOxBYKpbwCS3N5}yccs{dd87^xsyGRdnjp)P;Hd< z@b%6NkW3_vsBq7M11a2*>y}vS0*GXy_ zsOJ&WOXQgYmF=5x-^L2-Tz>CI*14Y$M(_C7Nx(?=4=-N1fpzY- z?(T|<1$zy0_hk||Ch+{)t@$%uC}!NU4)<|lJV{>7TmRb{yZF)G`_5r06cVo5)IyN^ zI_fN|jY6f>OKa|Sf&+88C06jT(ERyQ>i~08oVurZAeS?P^4H{f>8)!AT&Pmuu-gs& z?=P(^bDxt_JrmNtn?y39otF_nPBegT5Ws#hfENhhC^CSylK~);8bFBvR$~CW6Tq=) z07nr(el~#92_T^i;9~@E6dJ${1d!SWa32BeBm?+60c@xNw7(YsGM)kKL;$I007ntP zt}}ph2w)!@z|{nhV-4VY1aK4^z|#bfhYese0Td7hFyj;e$dm@K9|7z&12~faa<&0{ zkpRj<19*@C4mksOg#eCw14yn0fFscW<`O`*F@OUIATJxh$pld98NeU`999N!0|6A< z2C#_$iaY~|uK|D(!~mueK&fK@IRYr+4PYe!lr#oVA5UEUBvWt%m=qfcpi(h_8wj9s zFo5q7KsjLm4-r6dWB{LmtP*7@s7}9!!j6`Q5wwMYT7n&<3 zvYC3=+*1pid+K0wPYrDDsee%f6|VtN?dtnLa5QbMk73yIZv>m`0Bv&}Q8=;A=31m} zuH6g!XqyZ6n%G>)LRmF|(b7bpSE9}<(dLyX^GZBo zI#HbaF}2dM6v6Hvq##j{&2lRWa!vVo+Dx5$oa?sNJ5?WGG%g>E`bH*9S!J3 zf?$A0%QFn<1%SM27?fGYAp)wgTmYA4Q;UMn5yoKOGM4{TGA_jiXu{RfG6JI2YX($E z5N-Jz2J{z#7`s}&#(=z7+A>W|p|ux3TP7n4=t*YW3`b!3cmsNgAcoPF4>TYzW>|gC zl+5r8rh1F1wl}FBA&4=q<%|KnNzj`Fg$+nhh3y|LgBuhQLKb=>%V?Sf;wnw~!2pQf z%`)elf<0Zb0YX8*M#b~sy_S_c#naM#uOo%^_!oJUD1Ct{&-AMb$_*LV-tuRa#oPO- z(!8vo*kkNDv-3{wkAN0Bog5}R(ZWP0_j_KkThiYCDL$Bwpbh*BSD%0iH=AMpZr#UE z_2C`X{Z7{XZr1&Y*8N%5{bknuRo4ACynAco4Yw=g&BmLBPVQ-FK)mh-YpM96i-7ZD z`i_c;a4vySzqXK=DJ==Tc6TzWZ}AE6gK~!=uRIT3P+Gb&86EsIbZZ5HbV-$J%|CZt zxgr_O?Fz_R1={uUU5`|E7vC!{@o*1T@}6CKG?ujox={d@o_QIVjvvv@;Sd%e1SkDU z3b<$WBfGBjbI(djX5ypDJ91|7!H<3CU(RcbSHKt7B>pUE*V;(9Z|zo=v~tDT+%{;< z>in5uESYKo392ZO7z-Bp2P55+4%?999;`DiJZL|7huWQ{wUUl5i?xypcb2c`^|~yn z!Zi3&qyxNs`jO=EGV7!4z;n^ihgCm8xx8_Kvp2m~g2mzLxwNQog2v}SctEbW<>b<2 zXy?6ud~)x;PPK3VtVTW>sL=2H}i+sB*(kedOxwnKB%dCwIp zsmbioJ}+E-$QvHv>Pz0e87edIMO2qxH(IW5WQZ3zOip$>2A~WgfV8*g79$b1M>BL8 zd=?2E3;bu$f1O*3Hw}k{GF@w-+nf|i$%qf}VXrLD-h`2)56;FnrmsNC`Hx4dPw}kN zjps~R8naFXjpjS6uK;O|*(UMLO6*%WhtpUP9>!XV2XcFr3aN$*Ni8w4K8e)}%OE1H z(HGR~QU{gC+j~i@R#xcdZN@u9&(yRiI{NC&F{Y#7WcGTsC zQ=IB0@Ip(0X7KbM2mnhkfVVlkSd{^cp9TO+GJyIGtJ0SkHf&GbENdBCRn^qOxkXG; zH^~W(Emq97NSh0~&G|xEw`ziJQ(U#Fx7!If)ZKQ>JzoXtN}xx626D|HltUh?dTPk?Mut4x zztQ||4)psy0vgyBw9S79r+@zH#Ma))PLl{1fvQ_2q>Wq?Mm^lo@?{vbEz=?>nHLI* ziPyAiK|zQ|VQ(GfAhd;lk?yz7+p)a_-e-Oe3sWl%6sF6`~$FZjYLpqi)kBG^qT%ycRT zku97GPT#brhZMKcvq2m9=5Tc%k0ry^UdDOI(bcwSFsgQ^%uo(!WrwyW>c=?6sFN+l z3oX^7P(;4Ej=$|>Xq>v1%wN_W8VZGm@<`kLpa0;lfV&L-az_HSp(4v*n{LK@1ZPEt0n2{0hBvQRrl6fISiwuuqkO1GKK5XR>xWr&bfeOCHBDS zZIq;XZ!2(WLP<@gq>j_@y{fIn$q~BtCBy349L>yWi*`degjPuTtCH9`-WIbV%@LKh zrAb{|<46&=Q?w)iN!XCqXxD_}W2(es*K~CsAIEdTJWpD;>ezhJLBEMzvj*38_Mrrr zsJOhS3OH7@+74zmj9WPnKK^5@d+^@ zg!631gH9G`oP%a4=0FR$2Hy$X6U1Fp540Q#h33ZVmuPnTjXaGK(YkeKpg@RXLL^d+Ah19xt zK+0+(CW9kTXe10%N(#?GAaEhh2Z$vfd4)SfDtU|!IiYEwPm&gWnk`gX41c%EhPjaj zY`wy{aI#jX&=o`M>R2JB)P}}t#0(>~e8dbRk)$p|KI4QpkMk^uM$`CA_77-78tscK z|6nL%U4e#j2^V-RX4QBDi<1{e9-JDJ)Xs-hS?PgsG6#FbJ%~zjb@BfE-;rBAZB6cz zhp!8^vEaK3xI)e$Uar1od{oUw`M-mNOvQZ*n-uV@U^5lJHhE}>Q*lX#skoYa>!eNV z_W?u$|7-1!lwsP+jCwI1t^4wXtX!n2aeYg)=#+V%2*6?v~ z3wV=beK?Z%Wb^*S4Z%X%H*4|c9AF0CgpyOd$s2GNFHTMUGD(m(J%~!;^XAs(25$~m zm*LH`(68GIZ%WM{Ko6@7SHd$Tp!0lCGuYL_L)g`N9=;}yxW-mRW4U8Uw7|5GWqG_e zI@9W;nrT_Sm(iQ^{SWgjQ8GMBghu9BrVR2d-I{;9o!o_F+(l3y zSmwBZ_jANb#_Mnl*?TLF31r{1$4$*4Fdzm6NR-8>-p5wL={* znd$!AB?pH-J1g6L#{0iJ@3S4^%9)i(NFQ#Tm5p!ANWzR9J}Jjb21f+Z@ECja5uuan z5kb_vjCuirKGpx>F+rrvm>^0cj|rv>j)_s^_!VSTC-+fn2wcA9LjYclUeuZ$+JTr^ zUX8P$j`GzD`0IN$kQz(Qk&dCAywn}y15v3z7EC>u8QNLOE=!&T_?+#CB@#->jZ(D<@YZ6{jvQ1g1E~p;dh&%W%!*ov^##g5<`3Nk{nvjOY_j4ytE8q$zAN1 zaUru#>g>^qS{(e>qudSHZ2TATkA)mr4P1gNB^s=XP~m{;3(|mp$#45Q(l;-^yUFjd z@;fBISIX}V^81kdz9zrhEoH8^dGC{;(Ng>}d86J=+#8L79NPyMkz>4p9OK2Qsb3}u z$}tb3lK66LYjZ=64Of@Z#lD?bUo-7Qt5T6r*7NYiQbN@iRJ^}V<+2n{PK+*KbW$x~ zShAP#f6Y!LPDZ{EqmktcQ;u1_d{xPp>n-{6l`WSqjl6mDaJ;!MFavMyhl}tgZ@`

oJOzohUUc9m@>Vu>w*$RzS29Ej)x>t>@uu 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 z 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 HJMdwNoMg)?QIN(F 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 azd!$D9EdgIfx9;Qf2@N4Mg0FT2mTKd;b*M? literal 0 HcmV?d00001 diff --git a/lib/Loreline.xml b/lib/Loreline.xml new file mode 100644 index 0000000..8e43142 --- /dev/null +++ b/lib/Loreline.xml @@ -0,0 +1,495 @@ + + + + Loreline + + + +

+ Base interface to hold loreline values. + This interface allows to map loreline object fields to game-specific objects. + + + + + Called when the object has been created from an interpreter + + The interpreter instance + + + + Get the value associated to the given field key + + The interpreter instance + The field key + The value associated with the key + + + + Set the value associated to the given field key + + The interpreter instance + The field key + The value to set + + + + Remove the field associated to the given key + + The interpreter instance + The field key to remove + True if the key was found and removed, false otherwise + + + + Check if a value exists for the given key + + The interpreter instance + The field key to check + True if the key exists, false otherwise + + + + Get all the fields of this object + + The interpreter instance + An array of field keys + + + + Main interpreter class for Loreline scripts. + This class is responsible for executing a parsed Loreline script, + managing the runtime state, and interacting with the host application + through handler functions. + + + + + Represents a tag in text content, which can be used for styling or other purposes. + + + + + Whether this is a closing tag. + + + + + The value or name of the tag. + + + + + The offset in the text where this tag appears. + + + + + Represents a choice option presented to the user. + + + + + The text of the choice option. + + + + + Any tags associated with the choice text. + + + + + Whether this choice option is currently enabled. + + + + + Delegate type for functions that can be called from the script. + + The interpreter instance + Arguments passed to the function + The result of the function + + + + Callback type for dialogue continuation. + + + + + Contains information about a dialogue to be displayed to the user. + + + + + The interpreter instance. + + + + + The character speaking (null for narrator text). + + + + + The text content to display. + + + + + Any tags in the text. + + + + + Function to call when the text has been displayed. + + + + + Handler type for text output with callback. + This is called when the script needs to display text to the user. + + A Dialogue structure containing all necessary information + + + + Callback type for choice selection. + + The index of the selected choice + + + + Contains information about choices to be presented to the user. + + + + + The interpreter instance. + + + + + The available choice options. + + + + + Function to call with the index of the selected choice. + + + + + Handler type for choice presentation with callback. + This is called when the script needs to present choices to the user. + + A Choice structure containing all necessary information + + + + A custom instanciator to create fields objects. + + The interpreter related to this object creation + The expected type of the object, if there is any known + The associated node in the script, if any + + + + + Contains information about the script execution completion. + + + + + The interpreter instance. + + + + + Retrieve default interpreter options + + + + + Optional map of additional functions to make available to the script + + + + + Tells whether access is strict or not. If set to true, + trying to read or write an undefined variable will throw an error. + + + + + A custom instantiator to create fields objects. + + + + + Optional translations map for localization. + Built from a parsed translation file using Engine.ExtractTranslations(). + + + + + Handler type to be called when the execution finishes. + + A Finish structure containing the interpreter instance + + + + The underlying runtime interpreter instance. + + + + + Creates a new Loreline script interpreter. + + The parsed script to execute + Function to call when displaying dialogue text + Function to call when presenting choices + Function to call when execution finishes + + + + Creates a new Loreline script interpreter. + + The parsed script to execute + Function to call when displaying dialogue text + Function to call when presenting choices + Function to call when execution finishes + Additional options + + + + Starts script execution from the beginning or a specific beat. + + Optional name of the beat to start from. If null, execution starts from + the first beat or a beat named "_" if it exists. + Thrown if the specified beat doesn't exist or if no beats are found in the script + + + + Saves the current state of the interpreter. + This includes all state variables, character states, and execution stack, + allowing execution to be resumed later from the exact same point. + + A JSON string containing the serialized state + + + + Restores the interpreter state from a previously saved state. + This allows resuming execution from a previously saved state. + + The JSON string containing the serialized state + Thrown if the save data version is incompatible + + + + Resumes execution after restoring state. + This should be called after Restore() to continue execution. + + + + + Gets a character by name. + + The name of the character to get + The character's fields or null if the character doesn't exist + + + + Gets a specific field of a character. + + The name of the character + The name of the field to get + The field value or null if the character or field doesn't exist + + + + The main public API for Loreline runtime. + Provides easy access to the core functionality for parsing and running Loreline scripts. + + + + + Parses the given text input and creates an executable instance from it. + + + This is the first step in working with a Loreline script. The returned + object can then be passed to methods Play() or Resume(). + + The Loreline script content as a string (.lor format) + (optional) The file path of the input being parsed. If provided, requires `handleFile` as well. + (optional) A file handler to read imports. If that handler is asynchronous, then `parse()` method will return null and `callback` argument should be used to get the final script + If provided, will be called with the resulting script as argument. Mostly useful when reading file imports asynchronously + The parsed script as an AST instance (if loaded synchronously) + Thrown if the script contains syntax errors or other parsing issues + + + + Starts playing a Loreline script from the beginning or a specific beat. + + + This function takes care of initializing the interpreter and starting execution + immediately. You'll need to provide handlers for dialogues, choices, and + script completion. + + The parsed script (result from ) + Function called when dialogue text should be displayed + Function called when player needs to make a choice + Function called when script execution completes + Name of a specific beat to start from (defaults to first beat) + The interpreter instance that is running the script + + + + Starts playing a Loreline script from the beginning or a specific beat. + + + This function takes care of initializing the interpreter and starting execution + immediately. You'll need to provide handlers for dialogues, choices, and + script completion. + + The parsed script (result from ) + Function called when dialogue text should be displayed + Function called when player needs to make a choice + Function called when script execution completes + Additional options + The interpreter instance that is running the script + + + + Starts playing a Loreline script from the beginning or a specific beat. + + + This function takes care of initializing the interpreter and starting execution + immediately. You'll need to provide handlers for dialogues, choices, and + script completion. + + The parsed script (result from ) + Function called when dialogue text should be displayed + Function called when player needs to make a choice + Function called when script execution completes + Name of a specific beat to start from (defaults to first beat) + Additional options + The interpreter instance that is running the script + + + + Resumes a previously saved Loreline script from its saved state. + + + This allows you to continue a story from the exact point where it was saved, + restoring all state variables, choices, and player progress. + + The parsed script (result from ) + Function called when dialogue text should be displayed + Function called when player needs to make a choice + Function called when script execution completes + The saved game data (typically from ) + Optional beat name to override where to resume from + The interpreter instance that is running the script + + + + Resumes a previously saved Loreline script from its saved state. + + + This allows you to continue a story from the exact point where it was saved, + restoring all state variables, choices, and player progress. + + The parsed script (result from ) + Function called when dialogue text should be displayed + Function called when player needs to make a choice + Function called when script execution completes + The saved game data (typically from ) + Additional options + The interpreter instance that is running the script + + + + Resumes a previously saved Loreline script from its saved state. + + + This allows you to continue a story from the exact point where it was saved, + restoring all state variables, choices, and player progress. + + The parsed script (result from ) + Function called when dialogue text should be displayed + Function called when player needs to make a choice + Function called when script execution completes + The saved game data (typically from ) + Optional beat name to override where to resume from + Additional options + The interpreter instance that is running the script + + + + Extracts translations from a parsed translation script. + + + Given a translation file parsed with , this returns a translations map + that can be passed as to + Play() or Resume(). + + The parsed translation script (result from on a .XX.lor file) + A translations object to pass as + + + + Prints a parsed script back into Loreline source code. + + The parsed script (result from ) + The indentation string to use (defaults to two spaces) + The newline string to use (defaults to "\n") + The printed source code as a string + + + + Represents a node in a Loreline AST. + + + + + The underlying runtime node instance. + + + + + The type of the node as string + + + + + The id of this node (should be unique within a single script hierarchy) + + + + + Converts the node to a JSON representation. + This can be used for debugging or serialization purposes. + + Whether to format the JSON with indentation and line breaks + A JSON string representation of the node + + + + Represents the root node of a Loreline script AST. + + + + + The underlying runtime script instance. + + + + + Creates a new Script instance with the provided runtime script. + + The parsed runtime script to wrap + + + diff --git a/src/OpenTheBox/Adventures/AdventureEngine.cs b/src/OpenTheBox/Adventures/AdventureEngine.cs new file mode 100644 index 0000000..b92d49b --- /dev/null +++ b/src/OpenTheBox/Adventures/AdventureEngine.cs @@ -0,0 +1,258 @@ +using Loreline; +using OpenTheBox.Core; +using OpenTheBox.Core.Enums; +using OpenTheBox.Core.Items; +using OpenTheBox.Localization; +using OpenTheBox.Rendering; + +namespace OpenTheBox.Adventures; + +/// +/// Represents a single event produced during an adventure, such as granting an item +/// or modifying a resource. +/// +public sealed record AdventureEvent(GameEventKind Kind, string TargetId, int Amount = 1); + +/// +/// The kind of event that occurred during an adventure. +/// +public enum GameEventKind +{ + ItemGranted, + ItemRemoved, + ResourceAdded +} + +/// +/// Wraps the Loreline API for adventure playback. Loads .lor script files, +/// registers custom game functions, and bridges the callback-based Loreline engine +/// into async/await via . +/// +public sealed class AdventureEngine +{ + private static readonly string AdventuresRoot = Path.Combine("content", "adventures"); + + private readonly IRenderer _renderer; + private readonly LocalizationManager _loc; + + public AdventureEngine(IRenderer renderer, LocalizationManager loc) + { + _renderer = renderer; + _loc = loc; + } + + /// + /// Plays an adventure for the given theme, interacting with the player through the + /// renderer and collecting game events (items granted, resources modified, etc.). + /// + public async Task> PlayAdventure(AdventureTheme theme, GameState state) + { + string themeName = theme.ToString().ToLowerInvariant(); + string scriptPath = Path.Combine(AdventuresRoot, themeName, "intro.lor"); + + if (!File.Exists(scriptPath)) + { + _renderer.ShowError($"Adventure script not found: {scriptPath}"); + return []; + } + + string content = await File.ReadAllTextAsync(scriptPath); + var events = new List(); + string adventureId = $"{themeName}/intro"; + + // Parse the script, handling file imports synchronously + Script script = Engine.Parse( + content, + scriptPath, + (path, callback) => + { + string dir = Path.GetDirectoryName(scriptPath) ?? "."; + string importPath = Path.Combine(dir, path); + if (File.Exists(importPath)) + { + callback(File.ReadAllText(importPath)); + } + else + { + callback(string.Empty); + } + }); + + if (script is null) + { + _renderer.ShowError("Failed to parse adventure script."); + return []; + } + + // Build interpreter options with custom functions and optional translations + var options = Interpreter.InterpreterOptions.Default(); + options.Functions = BuildCustomFunctions(state, events); + + // Load translations if the current locale is not English + if (_loc.CurrentLocale != Locale.EN) + { + string localeSuffix = _loc.CurrentLocale.ToString().ToLowerInvariant(); + string translationPath = Path.Combine( + AdventuresRoot, themeName, $"intro.{localeSuffix}.lor"); + + if (File.Exists(translationPath)) + { + string translationContent = File.ReadAllText(translationPath); + Script translationScript = Engine.Parse(translationContent); + if (translationScript is not null) + { + options.Translations = Engine.ExtractTranslations(translationScript); + } + } + } + + // Use a TaskCompletionSource to bridge the callback-based API into await + var tcs = new TaskCompletionSource(); + + // Check for existing save data to resume + bool hasSave = state.AdventureSaveData.TryGetValue(adventureId, out string? saveData) + && !string.IsNullOrEmpty(saveData); + + Loreline.Interpreter interpreter; + + if (hasSave) + { + interpreter = Engine.Resume( + script, + dialogue => HandleDialogue(dialogue), + choice => HandleChoice(choice), + finish => HandleFinish(finish, tcs), + saveData!, + options: options); + } + else + { + interpreter = Engine.Play( + script, + dialogue => HandleDialogue(dialogue), + choice => HandleChoice(choice), + finish => HandleFinish(finish, tcs), + options: options); + } + + // Wait for the adventure to finish + await tcs.Task; + + // Clear the save data for this adventure since it completed + state.AdventureSaveData.Remove(adventureId); + + return events; + } + + /// + /// Saves the progress of the currently running adventure into the game state. + /// + public void SaveProgress(string adventureId, GameState state, Loreline.Interpreter interpreter) + { + string saveJson = interpreter.Save(); + state.AdventureSaveData[adventureId] = saveJson; + } + + // ── Loreline handlers ─────────────────────────────────────────────── + + private void HandleDialogue(Loreline.Interpreter.Dialogue dialogue) + { + _renderer.ShowAdventureDialogue(dialogue.Character, dialogue.Text); + _renderer.WaitForKeyPress(); + dialogue.Callback(); + } + + private void HandleChoice(Loreline.Interpreter.Choice choice) + { + var options = new List(); + foreach (var opt in choice.Options) + { + options.Add(opt.Enabled ? opt.Text : $"(unavailable) {opt.Text}"); + } + + int selectedIndex; + while (true) + { + selectedIndex = _renderer.ShowAdventureChoice(options); + + // Ensure the selected option is enabled + if (choice.Options[selectedIndex].Enabled) + break; + + _renderer.ShowError("That option is not available."); + } + + choice.Callback(selectedIndex); + } + + private static void HandleFinish( + Loreline.Interpreter.Finish finish, + TaskCompletionSource tcs) + { + tcs.TrySetResult(true); + } + + // ── Custom functions registered into the Loreline interpreter ──────── + + private Dictionary BuildCustomFunctions( + GameState state, + List events) + { + return new Dictionary + { + ["grantItem"] = (interpreter, args) => + { + if (args.Length < 1) return null!; + + string itemDefId = args[0]?.ToString() ?? string.Empty; + int quantity = args.Length >= 2 && args[1] is double d ? (int)d : 1; + + state.AddItem(ItemInstance.Create(itemDefId, quantity)); + events.Add(new AdventureEvent(GameEventKind.ItemGranted, itemDefId, quantity)); + + _renderer.ShowMessage(_loc.Get("adventure.item_granted", itemDefId, quantity)); + + return null!; + }, + + ["hasItem"] = (interpreter, args) => + { + if (args.Length < 1) return false; + + string itemDefId = args[0]?.ToString() ?? string.Empty; + return state.HasItem(itemDefId); + }, + + ["addResource"] = (interpreter, args) => + { + if (args.Length < 2) return null!; + + string resourceName = args[0]?.ToString() ?? string.Empty; + int amount = args[1] is double d ? (int)d : 0; + + events.Add(new AdventureEvent(GameEventKind.ResourceAdded, resourceName, amount)); + _renderer.ShowMessage(_loc.Get("adventure.resource_added", resourceName, amount)); + + return null!; + }, + + ["removeItem"] = (interpreter, args) => + { + if (args.Length < 1) return null!; + + string itemDefId = args[0]?.ToString() ?? string.Empty; + + // Find the first matching item and remove it + var item = state.Inventory.FirstOrDefault(i => i.DefinitionId == itemDefId); + if (item is not null) + { + state.RemoveItem(item.Id); + events.Add(new AdventureEvent(GameEventKind.ItemRemoved, itemDefId)); + _renderer.ShowMessage(_loc.Get("adventure.item_removed", itemDefId)); + } + + return null!; + } + }; + } +} diff --git a/src/OpenTheBox/Core/Boxes/BoxDefinition.cs b/src/OpenTheBox/Core/Boxes/BoxDefinition.cs new file mode 100644 index 0000000..0469872 --- /dev/null +++ b/src/OpenTheBox/Core/Boxes/BoxDefinition.cs @@ -0,0 +1,16 @@ +using OpenTheBox.Core.Enums; + +namespace OpenTheBox.Core.Boxes; + +/// +/// Static definition of a box type, including its loot table and opening requirements. +/// +public sealed record BoxDefinition( + string Id, + string NameKey, + string DescriptionKey, + ItemRarity Rarity, + LootTable LootTable, + bool IsAutoOpen, + List? RequiredItems = null +); diff --git a/src/OpenTheBox/Core/Boxes/LootCondition.cs b/src/OpenTheBox/Core/Boxes/LootCondition.cs new file mode 100644 index 0000000..d111cff --- /dev/null +++ b/src/OpenTheBox/Core/Boxes/LootCondition.cs @@ -0,0 +1,13 @@ +using OpenTheBox.Core.Enums; + +namespace OpenTheBox.Core.Boxes; + +/// +/// Condition that must be met for a loot entry to be eligible for dropping. +/// +public sealed record LootCondition( + LootConditionType Type, + string? TargetId = null, + string? Comparison = null, + float? Value = null +); diff --git a/src/OpenTheBox/Core/Boxes/LootEntry.cs b/src/OpenTheBox/Core/Boxes/LootEntry.cs new file mode 100644 index 0000000..8cf83db --- /dev/null +++ b/src/OpenTheBox/Core/Boxes/LootEntry.cs @@ -0,0 +1,11 @@ +namespace OpenTheBox.Core.Boxes; + +/// +/// A single entry in a loot table, mapping an item definition to a drop weight +/// with an optional eligibility condition. +/// +public sealed record LootEntry( + string ItemDefinitionId, + float Weight, + LootCondition? Condition = null +); diff --git a/src/OpenTheBox/Core/Boxes/LootTable.cs b/src/OpenTheBox/Core/Boxes/LootTable.cs new file mode 100644 index 0000000..a4ba62c --- /dev/null +++ b/src/OpenTheBox/Core/Boxes/LootTable.cs @@ -0,0 +1,10 @@ +namespace OpenTheBox.Core.Boxes; + +/// +/// A loot table containing entries that can be rolled when opening a box. +/// +public sealed record LootTable( + List Entries, + int GuaranteedRolls, + int RollCount +); diff --git a/src/OpenTheBox/Core/Characters/PlayerAppearance.cs b/src/OpenTheBox/Core/Characters/PlayerAppearance.cs new file mode 100644 index 0000000..73ecc6a --- /dev/null +++ b/src/OpenTheBox/Core/Characters/PlayerAppearance.cs @@ -0,0 +1,17 @@ +using OpenTheBox.Core.Enums; + +namespace OpenTheBox.Core.Characters; + +/// +/// Visual appearance configuration for the player character. +/// +public sealed record PlayerAppearance +{ + public HairStyle HairStyle { get; init; } + public TintColor HairTint { get; init; } + public EyeStyle EyeStyle { get; init; } + public BodyStyle BodyStyle { get; init; } + public LegStyle LegStyle { get; init; } + public ArmStyle ArmStyle { get; init; } + public TintColor BodyTint { get; init; } +} diff --git a/src/OpenTheBox/Core/Enums/AdventureTheme.cs b/src/OpenTheBox/Core/Enums/AdventureTheme.cs new file mode 100644 index 0000000..a699d75 --- /dev/null +++ b/src/OpenTheBox/Core/Enums/AdventureTheme.cs @@ -0,0 +1,36 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// The 9 thematic worlds for interactive adventures. +/// Each theme has its own locations, characters, key resources, moods, and environments. +/// Adventures are accessed via AdventureTokens found in Boite aventure. +/// +public enum AdventureTheme +{ + /// Science-fiction and space exploration. Key resource: Oxygen. + Space, + + /// Fantasy medieval setting with castles and dungeons. Key resources: Mana, Stamina. + Medieval, + + /// Golden age of piracy with ships and treasure islands. Key resources: Gold, Stamina. + Pirate, + + /// Modern-day urban thriller setting. Key resources: Energy, Gold. + Contemporary, + + /// Romance and human relationships. Key resources: Health (emotional), Mana (intuition). + Sentimental, + + /// Prehistoric era with primitive survival. Key resources: Food, Stamina. + Prehistoric, + + /// Cosmic scale with divine entities and parallel dimensions. Key resources: Mana, Energy. + Cosmic, + + /// Microscopic world of cells, atoms, and circuits. Key resources: Energy, Oxygen. + Microscopic, + + /// Dark fantasy with gothic horror elements. Key resources: Blood, Mana. Unlocks Blood resource. + DarkFantasy +} diff --git a/src/OpenTheBox/Core/Enums/ArmStyle.cs b/src/OpenTheBox/Core/Enums/ArmStyle.cs new file mode 100644 index 0000000..556c449 --- /dev/null +++ b/src/OpenTheBox/Core/Enums/ArmStyle.cs @@ -0,0 +1,29 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Available arm styles for the slot. +/// Includes natural arm variations and fantastical replacements. +/// +public enum ArmStyle +{ + /// No arm style equipped (default). + None, + + /// Short arms. Common rarity. + Short, + + /// Normal-length arms. Common rarity. + Regular, + + /// Elongated arms. Uncommon rarity. + Long, + + /// Mechanical prosthetic arms. Epic rarity. + Mechanical, + + /// Wings replacing arms, enabling flight. Legendary rarity. + Wings, + + /// An additional pair of arms (4 total). Mythic rarity. + ExtraPair +} diff --git a/src/OpenTheBox/Core/Enums/BodyStyle.cs b/src/OpenTheBox/Core/Enums/BodyStyle.cs new file mode 100644 index 0000000..0fa48df --- /dev/null +++ b/src/OpenTheBox/Core/Enums/BodyStyle.cs @@ -0,0 +1,26 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Available body styles for the slot. +/// Represents torso clothing and armor options. +/// +public enum BodyStyle +{ + /// Bare torso (default). Common rarity. + Naked, + + /// Standard everyday t-shirt. Common rarity. + RegularTShirt, + + /// Low-cut fashionable t-shirt. Uncommon rarity. + SexyTShirt, + + /// Elegant formal suit. Rare rarity. + Suit, + + /// Full plate armor. Epic rarity. + Armored, + + /// Robotic mechanical body. Legendary rarity. + Robotic +} diff --git a/src/OpenTheBox/Core/Enums/CosmeticSlot.cs b/src/OpenTheBox/Core/Enums/CosmeticSlot.cs new file mode 100644 index 0000000..8260dee --- /dev/null +++ b/src/OpenTheBox/Core/Enums/CosmeticSlot.cs @@ -0,0 +1,23 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// The 5 cosmetic equipment slots available for player customization. +/// Each slot can hold one style item and optionally a . +/// +public enum CosmeticSlot +{ + /// Head slot for hair styles. See . + Hair, + + /// Face slot for eye styles and glasses. See . + Eyes, + + /// Torso slot for shirts and armor. See . + Body, + + /// Lower body slot for pants, boots, and leg cosmetics. See . + Legs, + + /// Arm slot for arm styles and appendages. See . + Arms +} diff --git a/src/OpenTheBox/Core/Enums/EnvironmentType.cs b/src/OpenTheBox/Core/Enums/EnvironmentType.cs new file mode 100644 index 0000000..941e99f --- /dev/null +++ b/src/OpenTheBox/Core/Enums/EnvironmentType.cs @@ -0,0 +1,17 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Broad environment categories for adventure locations. +/// Determines the visual theme and available interactions within a scene. +/// +public enum EnvironmentType +{ + /// Wilderness, forests, caves, plains, and open landscapes. + Nature, + + /// Urban areas including villages, cities, stations, and interiors. + Town, + + /// Aquatic environments: oceans, rivers, underwater, and coastal areas. + Water +} diff --git a/src/OpenTheBox/Core/Enums/EyeStyle.cs b/src/OpenTheBox/Core/Enums/EyeStyle.cs new file mode 100644 index 0000000..8da3f02 --- /dev/null +++ b/src/OpenTheBox/Core/Enums/EyeStyle.cs @@ -0,0 +1,41 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Available eye styles for the slot. +/// Includes natural eye colors and various eyewear options. +/// +public enum EyeStyle +{ + /// No eye style equipped (default). + None, + + /// Blue eyes. Common rarity. + Blue, + + /// Green eyes. Common rarity. + Green, + + /// Red-orange eyes. Uncommon rarity. + RedOrange, + + /// Brown eyes. Common rarity. + Brown, + + /// Deep black eyes. Uncommon rarity. + Black, + + /// Classic sunglasses. Rare rarity. + Sunglasses, + + /// Aviator-style pilot glasses. Rare rarity. + PilotGlasses, + + /// Fighter pilot aircraft glasses. Epic rarity. + AircraftGlasses, + + /// Glowing cybernetic eye implants. Legendary rarity. + CyberneticEyes, + + /// Round golden magician spectacles. Epic rarity. + MagicianGlasses +} diff --git a/src/OpenTheBox/Core/Enums/FontStyle.cs b/src/OpenTheBox/Core/Enums/FontStyle.cs new file mode 100644 index 0000000..833c6d8 --- /dev/null +++ b/src/OpenTheBox/Core/Enums/FontStyle.cs @@ -0,0 +1,27 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Unlockable font styles for the CLI interface. +/// Found in Boite Meta. The default console font is always available; +/// additional fonts are unlocked as drops. +/// +public enum FontStyle +{ + /// The default system console font. Always available. + Default, + + /// Consolas monospaced font. Clean and technical. + Consolas, + + /// Firetruc display font. Bold and playful. + Firetruc, + + /// JetBrains Mono font. Developer-focused with ligatures. + Jetbrains, + + /// Vercel One font. Modern and sleek. + VercelOne, + + /// Toto Posted One font. Artistic and distinctive. + TotoPostedOne +} diff --git a/src/OpenTheBox/Core/Enums/HairStyle.cs b/src/OpenTheBox/Core/Enums/HairStyle.cs new file mode 100644 index 0000000..e751d0d --- /dev/null +++ b/src/OpenTheBox/Core/Enums/HairStyle.cs @@ -0,0 +1,33 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Available hair styles for the slot. +/// Styles range from Common to Mythic rarity. +/// Legendary+ styles are guaranteed drops from Boite legend'hair. +/// +public enum HairStyle +{ + /// No hair style equipped (default). + None, + + /// Classic short hair. Common rarity. + Short, + + /// Long flowing hair. Common rarity. + Long, + + /// Ponytail hairstyle. Uncommon rarity. + Ponytail, + + /// Elaborate braided hair. Rare rarity. + Braided, + + /// High-tech hairstyle with neon effects. Epic rarity. + Cyberpunk, + + /// Living flame hair that flickers and glows. Legendary rarity. + Fire, + + /// Hair made of shimmering stardust, constantly shifting. Mythic rarity. + StardustLegendary +} diff --git a/src/OpenTheBox/Core/Enums/InteractionResultType.cs b/src/OpenTheBox/Core/Enums/InteractionResultType.cs new file mode 100644 index 0000000..4c8ce85 --- /dev/null +++ b/src/OpenTheBox/Core/Enums/InteractionResultType.cs @@ -0,0 +1,29 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Types of results produced by the auto-activation system +/// when items interact with each other in the player's inventory. +/// +public enum InteractionResultType +{ + /// Opens a box or chest, revealing its contents. + OpenBox, + + /// Crafts a new item from materials at a workstation. + Craft, + + /// Transforms one item into another (e.g., via TransformationPentacle). + Transform, + + /// Consumes an item to restore a resource (potion, food). + Consume, + + /// Unlocks new content such as a location, adventure, or character. + Unlock, + + /// Combines multiple items into a single more powerful item. + Combine, + + /// Teleports the player to a new location. + Teleport +} diff --git a/src/OpenTheBox/Core/Enums/ItemCategory.cs b/src/OpenTheBox/Core/Enums/ItemCategory.cs new file mode 100644 index 0000000..33f446e --- /dev/null +++ b/src/OpenTheBox/Core/Enums/ItemCategory.cs @@ -0,0 +1,57 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Categories of items that can be found inside boxes. +/// Each category determines how the item behaves in the inventory +/// and which systems can interact with it. +/// +public enum ItemCategory +{ + /// A box that can be opened to reveal other items. + Box, + + /// A key that auto-activates when a matching lock or chest is present. + Key, + + /// A commemorative badge earned through progression or special events. + Badge, + + /// A map that can unlock locations when combined with exploration badges. + Map, + + /// A cosmetic item that changes the player's visual appearance. + Cosmetic, + + /// A raw, refined, or shaped material used in crafting recipes. + Material, + + /// A consumable item that restores resources or grants temporary buffs. + Consumable, + + /// A blueprint that allows construction of a new crafting workstation. + WorkstationBlueprint, + + /// A collectible fragment of the game's lore and narrative. + LoreFragment, + + /// A token granting access to a themed interactive adventure. + AdventureToken, + + /// A meta-upgrade that enhances the CLI interface itself. + Meta, + + /// A cookie that grants fortune messages, buffs, or meta effects. + Cookie, + + /// A music item representing ambient themes, jingles, or character themes. + Music, + + /// An item tied to the narrative that advances the story. + StoryItem, + + /// An item produced by a crafting workstation. + CraftedItem, + + /// An item required to complete a character's personal quest. + QuestItem +} diff --git a/src/OpenTheBox/Core/Enums/ItemRarity.cs b/src/OpenTheBox/Core/Enums/ItemRarity.cs new file mode 100644 index 0000000..5906241 --- /dev/null +++ b/src/OpenTheBox/Core/Enums/ItemRarity.cs @@ -0,0 +1,26 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Rarity tiers for items, cosmetics, and loot drops. +/// Higher rarities have lower drop weights and stronger effects. +/// +public enum ItemRarity +{ + /// The most common tier. Frequently found in all box types. + Common, + + /// Slightly rarer than Common. Minor upgrades over base items. + Uncommon, + + /// Noticeably rare. Provides meaningful improvements. + Rare, + + /// Very rare. Significant power or visual distinction. + Epic, + + /// Extremely rare. Guaranteed from Boite legend'hair and Boite legendaire. + Legendary, + + /// The rarest tier in the game. Items of mythical power and uniqueness. + Mythic +} diff --git a/src/OpenTheBox/Core/Enums/LegStyle.cs b/src/OpenTheBox/Core/Enums/LegStyle.cs new file mode 100644 index 0000000..8f60e80 --- /dev/null +++ b/src/OpenTheBox/Core/Enums/LegStyle.cs @@ -0,0 +1,32 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Available leg styles for the slot. +/// Includes clothing, boots, and fantastical lower-body replacements. +/// +public enum LegStyle +{ + /// No leg style equipped (default). + None, + + /// Bare legs. Common rarity. + Naked, + + /// Simple undergarment. Common rarity. + Slip, + + /// Casual shorts. Uncommon rarity. + Short, + + /// Stockings or tights. Uncommon rarity. + Panty, + + /// Jet-propelled boots for flight. Epic rarity. + RocketBoots, + + /// Pirate wooden leg. Rare rarity. + PegLeg, + + /// Tentacles replacing legs entirely. Legendary rarity. + Tentacles +} diff --git a/src/OpenTheBox/Core/Enums/Locale.cs b/src/OpenTheBox/Core/Enums/Locale.cs new file mode 100644 index 0000000..845e83e --- /dev/null +++ b/src/OpenTheBox/Core/Enums/Locale.cs @@ -0,0 +1,15 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Supported localization languages. +/// French is the primary language; English is secondary. +/// Character names are not localized; all other text content is. +/// +public enum Locale +{ + /// English (secondary language). + EN, + + /// French (primary language). + FR +} diff --git a/src/OpenTheBox/Core/Enums/LootConditionType.cs b/src/OpenTheBox/Core/Enums/LootConditionType.cs new file mode 100644 index 0000000..7888e28 --- /dev/null +++ b/src/OpenTheBox/Core/Enums/LootConditionType.cs @@ -0,0 +1,36 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Condition types used in loot tables to control item availability. +/// When a condition is not met, the corresponding entry is excluded +/// from the weighted loot roll and its weight is redistributed. +/// +public enum LootConditionType +{ + /// The player possesses a specific item in their inventory. + HasItem, + + /// The player does NOT possess a specific item in their inventory. + HasNotItem, + + /// A specified resource is above a given threshold value. + ResourceAbove, + + /// A specified resource is below a given threshold value. + ResourceBelow, + + /// The total number of boxes opened exceeds a threshold. + BoxesOpenedAbove, + + /// The player has unlocked a specific . + HasUIFeature, + + /// The player has built a specific . + HasWorkstation, + + /// The player has access to a specific . + HasAdventure, + + /// The player owns a specific cosmetic item. + HasCosmetic +} diff --git a/src/OpenTheBox/Core/Enums/MaterialForm.cs b/src/OpenTheBox/Core/Enums/MaterialForm.cs new file mode 100644 index 0000000..a6b258b --- /dev/null +++ b/src/OpenTheBox/Core/Enums/MaterialForm.cs @@ -0,0 +1,36 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Shapes that a can take. +/// Materials are transformed between forms at crafting workstations. +/// With 7 material types and 9 forms, the system offers 63 possible combinations. +/// +public enum MaterialForm +{ + /// Unprocessed material as found in loot drops. No station required. + Raw, + + /// Processed material ready for further shaping. Produced at Foundry. + Refined, + + /// Small fastener used in construction. Produced at Anvil. + Nail, + + /// Flat board, primarily for wood. Produced at SawingPost. + Plank, + + /// Solid bar, primarily for metals. Produced at Furnace. + Ingot, + + /// Thin flat piece. Produced at Forge. + Sheet, + + /// Thin strand for weaving and sewing. Produced at Loom. + Thread, + + /// Fine powder for alchemy and potions. Produced at MortarAndPestle. + Dust, + + /// Cut gemstone for jewelry and enchantment. Produced at Jewelry station. + Gem +} diff --git a/src/OpenTheBox/Core/Enums/MaterialType.cs b/src/OpenTheBox/Core/Enums/MaterialType.cs new file mode 100644 index 0000000..6509e0e --- /dev/null +++ b/src/OpenTheBox/Core/Enums/MaterialType.cs @@ -0,0 +1,30 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// The 7 material types used in crafting, ordered by tier (1-7). +/// Higher-tier materials are rarer and produce more powerful crafted items. +/// Each material can exist in any of the shapes. +/// +public enum MaterialType +{ + /// Tier 1. The most basic material. Common drop. + Wood, + + /// Tier 2. A simple alloy. Common drop. + Bronze, + + /// Tier 3. An intermediate metal. Uncommon drop. + Iron, + + /// Tier 4. An advanced alloy. Rare drop. + Steel, + + /// Tier 5. High-technology metal. Epic drop. + Titanium, + + /// Tier 6. Precious crystalline material. Legendary drop. + Diamond, + + /// Tier 7. The ultimate synthetic material. Mythic drop. + CarbonFiber +} diff --git a/src/OpenTheBox/Core/Enums/Mood.cs b/src/OpenTheBox/Core/Enums/Mood.cs new file mode 100644 index 0000000..4e1644f --- /dev/null +++ b/src/OpenTheBox/Core/Enums/Mood.cs @@ -0,0 +1,27 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Narrative moods that color the tone of adventures and story events. +/// Each supports a subset of moods +/// that influence dialogue, encounters, and outcomes. +/// +public enum Mood +{ + /// Grim, ominous, and foreboding atmosphere. + Dark, + + /// Light-hearted, humorous, and playful tone. + Comedy, + + /// Sorrowful events with loss and sacrifice. + Tragedy, + + /// Romantic relationships and emotional connections. + Romance, + + /// Eerie, unsettling, and frightening ambiance. + Spooky, + + /// Mystery-driven narrative focused on clues and deduction. + Investigation +} diff --git a/src/OpenTheBox/Core/Enums/ResourceType.cs b/src/OpenTheBox/Core/Enums/ResourceType.cs new file mode 100644 index 0000000..0e1ad8b --- /dev/null +++ b/src/OpenTheBox/Core/Enums/ResourceType.cs @@ -0,0 +1,33 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// The 8 player resources that govern survival, exploration, crafting, and combat. +/// Each resource has a maximum cap that can be upgraded via Boite d'amelioration. +/// Resources are unlocked progressively through gameplay. +/// +public enum ResourceType +{ + /// Hit points. Unlocked from the start. Used for survival and combat. + Health, + + /// Magic points. Unlocked on first encounter with a magical character. + Mana, + + /// Nourishment. Unlocked on first adventure explored. + Food, + + /// Physical endurance. Unlocked on first physical action (combat, craft). + Stamina, + + /// Blood resource. Unlocked with the DarkFantasy theme. Used for dark rituals. + Blood, + + /// Currency. Unlocked when a trading location is discovered. + Gold, + + /// Breathable supply. Unlocked with Space theme or underwater adventures. + Oxygen, + + /// Power supply. Unlocked when the first crafting workstation is built. + Energy +} diff --git a/src/OpenTheBox/Core/Enums/StatType.cs b/src/OpenTheBox/Core/Enums/StatType.cs new file mode 100644 index 0000000..7041429 --- /dev/null +++ b/src/OpenTheBox/Core/Enums/StatType.cs @@ -0,0 +1,26 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Player character statistics that influence gameplay outcomes. +/// Stats can be increased via Boite d'amelioration and leveling. +/// +public enum StatType +{ + /// Physical power. Affects melee damage and carrying capacity. + Strength, + + /// Mental acuity. Affects magic damage and crafting efficiency. + Intelligence, + + /// Fortune factor. Affects loot drop probabilities and critical chances. + Luck, + + /// Social influence. Affects merchant prices and dialogue options. + Charisma, + + /// Agility and precision. Affects dodge chance, speed, and accuracy. + Dexterity, + + /// Insight and intuition. Affects mana regeneration and perception. + Wisdom +} diff --git a/src/OpenTheBox/Core/Enums/TextColor.cs b/src/OpenTheBox/Core/Enums/TextColor.cs new file mode 100644 index 0000000..e7bf85a --- /dev/null +++ b/src/OpenTheBox/Core/Enums/TextColor.cs @@ -0,0 +1,39 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Semantic color slots for text rendering in the CLI interface. +/// Each slot can be customized by the player after unlocking . +/// Additional color slots are unlocked via Boite Meta drops. +/// +public enum TextColor +{ + /// Primary text color for general interface elements. + Primary, + + /// Secondary text color for supporting information. + Secondary, + + /// Tertiary text color for less prominent details. + Tertiary, + + /// Color used for character and player names. + Name, + + /// Color used for item names in the inventory and loot displays. + Item, + + /// Color used for quantity numbers. + Quantity, + + /// Color used for box names and box-related text. + Box, + + /// Color used for equipment names (armor, accessories). + Equipment, + + /// Color used for weapon names. + Weapon, + + /// Color used for material names and crafting ingredients. + Material +} diff --git a/src/OpenTheBox/Core/Enums/TintColor.cs b/src/OpenTheBox/Core/Enums/TintColor.cs new file mode 100644 index 0000000..8f89db6 --- /dev/null +++ b/src/OpenTheBox/Core/Enums/TintColor.cs @@ -0,0 +1,45 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Tint colors that can be applied to any . +/// Tints modify the visual color of the equipped cosmetic item. +/// Found in Boite stylee. +/// +public enum TintColor +{ + /// No tint applied; original color preserved. + None, + + /// Cyan tint. Common rarity. + Cyan, + + /// Orange tint. Common rarity. + Orange, + + /// Purple tint. Uncommon rarity. + Purple, + + /// Warm pink tint. Uncommon rarity. + WarmPink, + + /// Lightened version of the base color. Common rarity. + Light, + + /// Darkened version of the base color. Common rarity. + Dark, + + /// Multicolored rainbow effect. Epic rarity. + Rainbow, + + /// Bright neon glow effect. Rare rarity. + Neon, + + /// Metallic silver tint. Rare rarity. + Silver, + + /// Metallic gold tint. Epic rarity. + Gold, + + /// Absolute black with starfield reflections. Legendary rarity. + Void +} diff --git a/src/OpenTheBox/Core/Enums/UIFeature.cs b/src/OpenTheBox/Core/Enums/UIFeature.cs new file mode 100644 index 0000000..cef4c92 --- /dev/null +++ b/src/OpenTheBox/Core/Enums/UIFeature.cs @@ -0,0 +1,45 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// Unlockable CLI interface features that define the visual and interactive +/// progression of the game. Found exclusively in Boite Meta. +/// Phases 0-8 progressively transform the raw console into a full Spectre.Console layout. +/// +public enum UIFeature +{ + /// Phase 1: Basic 8-color ANSI text coloring for items, names, and quantities. + TextColors, + + /// Phase 2: Extended 256-color ANSI palette with gradients and shading. + ExtendedColors, + + /// Phase 3: Navigate menus with arrow keys instead of typing commands. + ArrowKeySelection, + + /// Phase 4: Persistent side panel displaying the player's inventory. + InventoryPanel, + + /// Phase 5: Resource bars (HP, Mana, Food, etc.) displayed as bar charts. + ResourcePanel, + + /// Phase 5: Player statistics panel showing Strength, Intelligence, etc. + StatsPanel, + + /// Phase 6: ASCII art portrait reflecting equipped cosmetics. + PortraitPanel, + + /// Phase 6: Chat panel for NPC dialogues and narrative events. + ChatPanel, + + /// Phase 8: Complete multi-panel layout with all UI elements organized. + FullLayout, + + /// Phase 8: Keyboard shortcuts for all major actions. + KeyboardShortcuts, + + /// Phase 7: Animated box-opening sequences with scrolling text and ASCII effects. + BoxAnimation, + + /// Phase 7: Dedicated crafting panel for material transformation and item creation. + CraftingPanel +} diff --git a/src/OpenTheBox/Core/Enums/WorkstationType.cs b/src/OpenTheBox/Core/Enums/WorkstationType.cs new file mode 100644 index 0000000..ee3387c --- /dev/null +++ b/src/OpenTheBox/Core/Enums/WorkstationType.cs @@ -0,0 +1,105 @@ +namespace OpenTheBox.Core.Enums; + +/// +/// The 32 crafting workstation types that can be built from blueprints. +/// Workstations transform materials between forms and produce crafted items. +/// Blueprints are found in Boite d'amelioration and Boite legendaire. +/// +public enum WorkstationType +{ + /// Refines raw materials into processed form. (Raw -> Refined) + Foundry, + + /// General-purpose crafting surface for basic recipes. + Workbench, + + /// High-heat oven for smelting ingots. (Refined -> Ingot) + Furnace, + + /// Weaving machine for producing threads. (Refined -> Thread) + Loom, + + /// Heavy metalworking surface for nails and tools. (Ingot -> Nail) + Anvil, + + /// Mystical table for brewing potions and elixirs. + AlchemyTable, + + /// Advanced metalworking station for sheets and weapons. (Ingot -> Sheet) + Forge, + + /// Lumber processing station. (Wood -> Plank) + SawingPost, + + /// Wind-powered grain processing station. + Windmill, + + /// Water-powered hydraulic transformation station. + Watermill, + + /// Press for extracting oils from raw materials. + OilPress, + + /// Workshop for creating ceramic objects. + PotteryWorkshop, + + /// Tailoring surface for creating clothing and cosmetics. + TailorTable, + + /// Grinding station for producing fine powders. (Refined -> Dust) + MortarAndPestle, + + /// Basin for applying and creating tint colors for cosmetics. + DyeBasin, + + /// Precision workstation for cutting gems and crafting jewelry. (Refined -> Gem) + Jewelry, + + /// Smoking chamber for food preservation. + Smoker, + + /// Large vat for brewing beverages and potions. + BrewingVat, + + /// Technical desk for designing advanced blueprints. + EngineerDesk, + + /// Station for joining metal components together. + WeldingStation, + + /// Drafting table for creating plans and schematics. + DrawingTable, + + /// Bench for detailed engraving and enchantment work. + EngravingBench, + + /// Station for stitching threads into garments. (Thread -> clothing) + SewingPost, + + /// Enchanted cauldron for brewing powerful magical potions. + MagicCauldron, + + /// Arcane pentacle for transforming objects into different forms. + TransformationPentacle, + + /// Creative space for artistic decoration and painting. + PaintingSpace, + + /// Apparatus for distilling alcohols and essences. + Distillery, + + /// Modern fabrication device for producing complex objects. + Printer3D, + + /// Advanced device for synthesizing rare materials from common ones. + MatterSynthesizer, + + /// High-tech station for biological and genetic modifications. + GeneticModStation, + + /// Temporal device enabling time manipulation in crafting. + TemporalBracelet, + + /// Preservation chamber for long-term item evolution and stasis. + StasisChamber +} diff --git a/src/OpenTheBox/Core/GameState.cs b/src/OpenTheBox/Core/GameState.cs new file mode 100644 index 0000000..46d6bd8 --- /dev/null +++ b/src/OpenTheBox/Core/GameState.cs @@ -0,0 +1,105 @@ +using OpenTheBox.Core.Characters; +using OpenTheBox.Core.Enums; +using OpenTheBox.Core.Items; + +namespace OpenTheBox.Core; + +/// +/// Represents the current/max pair for a resource. +/// +public sealed record ResourceState(int Current, int Max); + +/// +/// The complete player state, containing all inventory, progression, and configuration data. +/// +public sealed class GameState +{ + public required string PlayerName { get; set; } + public required PlayerAppearance Appearance { get; set; } + public required List Inventory { get; set; } + public required Dictionary Resources { get; set; } + public required Dictionary Stats { get; set; } + public required HashSet UnlockedUIFeatures { get; set; } + public required HashSet UnlockedWorkstations { get; set; } + public required HashSet UnlockedAdventures { get; set; } + public required HashSet UnlockedCosmetics { get; set; } + public required HashSet CompletedAdventures { get; set; } + public required Dictionary AdventureSaveData { get; set; } + public required HashSet VisibleResources { get; set; } + public required HashSet VisibleStats { get; set; } + public required int TotalBoxesOpened { get; set; } + public required Locale CurrentLocale { get; set; } + public required DateTime CreatedAt { get; set; } + public required TimeSpan TotalPlayTime { get; set; } + public required HashSet AvailableFonts { get; set; } + public required HashSet AvailableTextColors { get; set; } + + /// + /// Returns the current value of a resource, or 0 if the resource is not tracked. + /// + public int GetResource(ResourceType type) + => Resources.TryGetValue(type, out var state) ? state.Current : 0; + + /// + /// Returns true if the inventory contains at least one item with the given definition id. + /// + public bool HasItem(string defId) + => Inventory.Any(i => i.DefinitionId == defId); + + /// + /// Counts the total quantity of items matching the given definition id. + /// + public int CountItems(string defId) + => Inventory.Where(i => i.DefinitionId == defId).Sum(i => i.Quantity); + + /// + /// Returns true if the given UI feature has been unlocked. + /// + public bool HasUIFeature(UIFeature feature) + => UnlockedUIFeatures.Contains(feature); + + /// + /// Adds an item instance to the inventory. + /// + public void AddItem(ItemInstance item) + => Inventory.Add(item); + + /// + /// Removes an item instance from the inventory by its unique instance id. + /// Returns true if the item was found and removed. + /// + public bool RemoveItem(Guid id) + { + var item = Inventory.FirstOrDefault(i => i.Id == id); + if (item is null) + return false; + + return Inventory.Remove(item); + } + + /// + /// Factory method to create a new GameState with empty collections and sensible defaults. + /// + public static GameState Create(string name, Locale locale) => new() + { + PlayerName = name, + Appearance = new PlayerAppearance(), + Inventory = [], + Resources = [], + Stats = [], + UnlockedUIFeatures = [], + UnlockedWorkstations = [], + UnlockedAdventures = [], + UnlockedCosmetics = [], + CompletedAdventures = [], + AdventureSaveData = [], + VisibleResources = [], + VisibleStats = [], + TotalBoxesOpened = 0, + CurrentLocale = locale, + CreatedAt = DateTime.UtcNow, + TotalPlayTime = TimeSpan.Zero, + AvailableFonts = [], + AvailableTextColors = [] + }; +} diff --git a/src/OpenTheBox/Core/Interactions/InteractionResult.cs b/src/OpenTheBox/Core/Interactions/InteractionResult.cs new file mode 100644 index 0000000..275ac56 --- /dev/null +++ b/src/OpenTheBox/Core/Interactions/InteractionResult.cs @@ -0,0 +1,11 @@ +namespace OpenTheBox.Core.Interactions; + +/// +/// The result of executing an interaction rule, describing what was consumed and produced. +/// +public sealed record InteractionResult( + string RuleId, + List ConsumedItemIds, + List ProducedItemIds, + string Message +); diff --git a/src/OpenTheBox/Core/Interactions/InteractionRule.cs b/src/OpenTheBox/Core/Interactions/InteractionRule.cs new file mode 100644 index 0000000..ff502e1 --- /dev/null +++ b/src/OpenTheBox/Core/Interactions/InteractionRule.cs @@ -0,0 +1,18 @@ +using OpenTheBox.Core.Enums; + +namespace OpenTheBox.Core.Interactions; + +/// +/// Defines a rule for item interactions, specifying what items are required +/// and what result is produced when the interaction fires. +/// +public sealed record InteractionRule( + string Id, + List RequiredItemTags, + List? RequiredItemIds, + InteractionResultType ResultType, + string? ResultData, + bool IsAutomatic, + int Priority, + string DescriptionKey +); diff --git a/src/OpenTheBox/Core/Items/ItemDefinition.cs b/src/OpenTheBox/Core/Items/ItemDefinition.cs new file mode 100644 index 0000000..f190028 --- /dev/null +++ b/src/OpenTheBox/Core/Items/ItemDefinition.cs @@ -0,0 +1,24 @@ +using OpenTheBox.Core.Enums; + +namespace OpenTheBox.Core.Items; + +/// +/// Static template for an item. Defines the blueprint from which item instances are created. +/// +public sealed record ItemDefinition( + string Id, + string NameKey, + string DescriptionKey, + ItemCategory Category, + ItemRarity Rarity, + HashSet Tags, + UIFeature? MetaUnlock = null, + CosmeticSlot? CosmeticSlot = null, + string? CosmeticValue = null, + ResourceType? ResourceType = null, + int? ResourceAmount = null, + MaterialType? MaterialType = null, + MaterialForm? MaterialForm = null, + WorkstationType? WorkstationType = null, + AdventureTheme? AdventureTheme = null +); diff --git a/src/OpenTheBox/Core/Items/ItemInstance.cs b/src/OpenTheBox/Core/Items/ItemInstance.cs new file mode 100644 index 0000000..f91efe4 --- /dev/null +++ b/src/OpenTheBox/Core/Items/ItemInstance.cs @@ -0,0 +1,18 @@ +namespace OpenTheBox.Core.Items; + +/// +/// Runtime instance of an item in inventory, referencing an by its id. +/// +public sealed record ItemInstance( + Guid Id, + string DefinitionId, + int Quantity = 1, + Dictionary? Metadata = null +) +{ + /// + /// Creates a new item instance with an auto-generated id. + /// + public static ItemInstance Create(string definitionId, int quantity = 1, Dictionary? metadata = null) + => new(Guid.NewGuid(), definitionId, quantity, metadata); +} diff --git a/src/OpenTheBox/Data/ContentRegistry.cs b/src/OpenTheBox/Data/ContentRegistry.cs new file mode 100644 index 0000000..3ff091f --- /dev/null +++ b/src/OpenTheBox/Data/ContentRegistry.cs @@ -0,0 +1,83 @@ +using System.Text.Json; +using OpenTheBox.Core.Boxes; +using OpenTheBox.Core.Interactions; +using OpenTheBox.Core.Items; + +namespace OpenTheBox.Data; + +/// +/// Central registry for all game content definitions (items, boxes, interaction rules). +/// Content is loaded from data files and registered here for lookup by the simulation engines. +/// +public class ContentRegistry +{ + private static readonly JsonSerializerOptions JsonOptions = new() + { + PropertyNameCaseInsensitive = true, + Converters = { new System.Text.Json.Serialization.JsonStringEnumConverter() } + }; + + private readonly Dictionary _items = []; + private readonly Dictionary _boxes = []; + private readonly List _interactionRules = []; + + public void RegisterItem(ItemDefinition item) => _items[item.Id] = item; + public void RegisterBox(BoxDefinition box) => _boxes[box.Id] = box; + public void RegisterInteractionRule(InteractionRule rule) => _interactionRules.Add(rule); + + public ItemDefinition? GetItem(string id) => _items.GetValueOrDefault(id); + public BoxDefinition? GetBox(string id) => _boxes.GetValueOrDefault(id); + + /// + /// Returns true if the given definition id corresponds to a registered box. + /// + public bool IsBox(string id) => _boxes.ContainsKey(id); + + public IReadOnlyDictionary Items => _items; + public IReadOnlyDictionary Boxes => _boxes; + public IReadOnlyList InteractionRules => _interactionRules; + + /// + /// Loads content definitions from JSON files and returns a populated registry. + /// Files that do not exist are silently skipped. + /// + public static ContentRegistry LoadFromFiles(string itemsPath, string boxesPath, string interactionsPath) + { + var registry = new ContentRegistry(); + + if (File.Exists(itemsPath)) + { + var json = File.ReadAllText(itemsPath); + var items = JsonSerializer.Deserialize>(json, JsonOptions); + if (items is not null) + { + foreach (var item in items) + registry.RegisterItem(item); + } + } + + if (File.Exists(boxesPath)) + { + var json = File.ReadAllText(boxesPath); + var boxes = JsonSerializer.Deserialize>(json, JsonOptions); + if (boxes is not null) + { + foreach (var box in boxes) + registry.RegisterBox(box); + } + } + + if (File.Exists(interactionsPath)) + { + var json = File.ReadAllText(interactionsPath); + var rules = JsonSerializer.Deserialize>(json, JsonOptions); + if (rules is not null) + { + foreach (var rule in rules) + registry.RegisterInteractionRule(rule); + } + } + + return registry; + } +} diff --git a/src/OpenTheBox/Localization/LocalizationManager.cs b/src/OpenTheBox/Localization/LocalizationManager.cs new file mode 100644 index 0000000..a51e727 --- /dev/null +++ b/src/OpenTheBox/Localization/LocalizationManager.cs @@ -0,0 +1,80 @@ +using System.Text.Json; +using OpenTheBox.Core.Enums; + +namespace OpenTheBox.Localization; + +/// +/// Loads and serves localized strings from JSON files stored at +/// content/strings/{locale}.json. Supports runtime locale switching. +/// +public sealed class LocalizationManager +{ + private static readonly string StringsDirectory = Path.Combine("content", "strings"); + + private Dictionary _strings = []; + + /// + /// The currently loaded locale. + /// + public Locale CurrentLocale { get; private set; } + + /// + /// Creates a new and immediately loads the + /// specified locale. + /// + public LocalizationManager(Locale locale) + { + Load(locale); + } + + /// + /// Loads (or reloads) the string table for the given locale. + /// + public void Load(Locale locale) + { + CurrentLocale = locale; + _strings = []; + + string localeName = locale.ToString().ToLowerInvariant(); + string path = Path.Combine(StringsDirectory, $"{localeName}.json"); + + if (!File.Exists(path)) + return; + + string json = File.ReadAllText(path); + var parsed = JsonSerializer.Deserialize>(json); + + if (parsed is not null) + { + _strings = parsed; + } + } + + /// + /// Returns the localized string for the given key, formatted with the optional + /// arguments using . + /// If the key is not found, returns "[MISSING:key]". + /// + public string Get(string key, params object[] args) + { + if (!_strings.TryGetValue(key, out string? value)) + { + return $"[MISSING:{key}]"; + } + + if (args.Length > 0) + { + return string.Format(value, args); + } + + return value; + } + + /// + /// Switches to a different locale, reloading the string table. + /// + public void Change(Locale locale) + { + Load(locale); + } +} diff --git a/src/OpenTheBox/OpenTheBox.csproj b/src/OpenTheBox/OpenTheBox.csproj new file mode 100644 index 0000000..450fd9e --- /dev/null +++ b/src/OpenTheBox/OpenTheBox.csproj @@ -0,0 +1,28 @@ + + + + Exe + net10.0 + enable + enable + OpenTheBox + + + + + + + + + ..\..\lib\Loreline.dll + + + + + + PreserveNewest + content\%(RecursiveDir)%(Filename)%(Extension) + + + + diff --git a/src/OpenTheBox/Persistence/SaveData.cs b/src/OpenTheBox/Persistence/SaveData.cs new file mode 100644 index 0000000..c126508 --- /dev/null +++ b/src/OpenTheBox/Persistence/SaveData.cs @@ -0,0 +1,28 @@ +using System.Text.Json.Serialization; +using OpenTheBox.Core; + +namespace OpenTheBox.Persistence; + +/// +/// Serializable wrapper around that includes save metadata. +/// +public sealed class SaveData +{ + /// + /// Schema version for forward/backward compatibility checks. + /// + [JsonPropertyName("version")] + public int Version { get; init; } = 1; + + /// + /// Timestamp of when the save was created. + /// + [JsonPropertyName("savedAt")] + public DateTime SavedAt { get; init; } = DateTime.UtcNow; + + /// + /// The complete game state snapshot. + /// + [JsonPropertyName("state")] + public required GameState State { get; init; } +} diff --git a/src/OpenTheBox/Persistence/SaveManager.cs b/src/OpenTheBox/Persistence/SaveManager.cs new file mode 100644 index 0000000..72dc348 --- /dev/null +++ b/src/OpenTheBox/Persistence/SaveManager.cs @@ -0,0 +1,127 @@ +using System.Text.Json; +using System.Text.Json.Serialization; +using OpenTheBox.Core; + +namespace OpenTheBox.Persistence; + +/// +/// Manages reading and writing save files to disk. +/// Save files are stored as indented JSON in the saves/ directory with the +/// .otb extension. +/// +public sealed class SaveManager +{ + private const string SaveDirectory = "saves"; + private const string Extension = ".otb"; + + private static readonly JsonSerializerOptions SerializerOptions = new() + { + WriteIndented = true, + Converters = { new JsonStringEnumConverter() }, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + /// + /// Ensures the save directory exists. + /// + private static void EnsureDirectory() + { + if (!Directory.Exists(SaveDirectory)) + { + Directory.CreateDirectory(SaveDirectory); + } + } + + /// + /// Builds the full file path for a given slot name. + /// + private static string SlotPath(string slotName) => + Path.Combine(SaveDirectory, $"{slotName}{Extension}"); + + /// + /// Saves the current game state to the specified slot. + /// + public void Save(GameState state, string slotName = "autosave") + { + EnsureDirectory(); + + var data = new SaveData + { + SavedAt = DateTime.UtcNow, + State = state + }; + + string json = JsonSerializer.Serialize(data, SerializerOptions); + File.WriteAllText(SlotPath(slotName), json); + } + + /// + /// Loads a game state from the specified slot. + /// Returns null if the slot does not exist or cannot be deserialized. + /// + public GameState? Load(string slotName = "autosave") + { + string path = SlotPath(slotName); + + if (!File.Exists(path)) + return null; + + string json = File.ReadAllText(path); + var data = JsonSerializer.Deserialize(json, SerializerOptions); + + return data?.State; + } + + /// + /// Lists all save slots with their names and save timestamps. + /// + public List<(string Name, DateTime SavedAt)> ListSlots() + { + EnsureDirectory(); + + var slots = new List<(string Name, DateTime SavedAt)>(); + + foreach (string file in Directory.GetFiles(SaveDirectory, $"*{Extension}")) + { + string slotName = Path.GetFileNameWithoutExtension(file); + + try + { + string json = File.ReadAllText(file); + var data = JsonSerializer.Deserialize(json, SerializerOptions); + if (data is not null) + { + slots.Add((slotName, data.SavedAt)); + } + } + catch (JsonException) + { + // Corrupted save file; include it with the file's last write time + slots.Add((slotName, File.GetLastWriteTimeUtc(file))); + } + } + + return slots.OrderByDescending(s => s.SavedAt).ToList(); + } + + /// + /// Returns true if a save slot with the given name exists on disk. + /// + public bool SlotExists(string slotName) + { + return File.Exists(SlotPath(slotName)); + } + + /// + /// Deletes the save file for the given slot. Does nothing if the slot does not exist. + /// + public void DeleteSlot(string slotName) + { + string path = SlotPath(slotName); + + if (File.Exists(path)) + { + File.Delete(path); + } + } +} diff --git a/src/OpenTheBox/Program.cs b/src/OpenTheBox/Program.cs new file mode 100644 index 0000000..cd13cfd --- /dev/null +++ b/src/OpenTheBox/Program.cs @@ -0,0 +1,422 @@ +using OpenTheBox.Core; +using OpenTheBox.Core.Enums; +using OpenTheBox.Core.Items; +using OpenTheBox.Data; +using OpenTheBox.Localization; +using OpenTheBox.Persistence; +using OpenTheBox.Rendering; +using OpenTheBox.Simulation; +using OpenTheBox.Simulation.Actions; +using OpenTheBox.Simulation.Events; +using OpenTheBox.Adventures; + +namespace OpenTheBox; + +public static class Program +{ + private static GameState _state = null!; + private static ContentRegistry _registry = null!; + private static LocalizationManager _loc = null!; + private static SaveManager _saveManager = null!; + private static GameSimulation _simulation = null!; + private static RenderContext _renderContext = null!; + private static IRenderer _renderer = null!; + private static bool _running = true; + + public static async Task Main(string[] args) + { + _saveManager = new SaveManager(); + _loc = new LocalizationManager(Locale.EN); + _renderContext = new RenderContext(); + _renderer = RendererFactory.Create(_renderContext); + + await MainMenuLoop(); + } + + private static async Task MainMenuLoop() + { + while (_running) + { + _renderer.Clear(); + _renderer.ShowMessage("========================================"); + _renderer.ShowMessage(" OPEN THE BOX"); + _renderer.ShowMessage("========================================"); + _renderer.ShowMessage(""); + _renderer.ShowMessage(_loc.Get("game.subtitle")); + _renderer.ShowMessage(""); + + var options = new List + { + _loc.Get("menu.new_game"), + _loc.Get("menu.load_game"), + _loc.Get("menu.language"), + _loc.Get("menu.quit") + }; + + int choice = _renderer.ShowSelection("", options); + + switch (choice) + { + case 0: await NewGame(); break; + case 1: await LoadGame(); break; + case 2: ChangeLanguage(); break; + case 3: _running = false; break; + } + } + } + + private static async Task NewGame() + { + string name = _renderer.ShowTextInput(_loc.Get("prompt.name")); + if (string.IsNullOrWhiteSpace(name)) name = "BoxOpener"; + + _state = GameState.Create(name, _loc.CurrentLocale); + InitializeGame(); + + var starterBox = ItemInstance.Create("box_starter"); + _state.AddItem(starterBox); + + _renderer.ShowMessage(""); + _renderer.ShowMessage($"Welcome, {name}!"); + _renderer.ShowMessage(_loc.Get("box.starter.desc")); + _renderer.WaitForKeyPress(_loc.Get("prompt.press_key")); + + await GameLoop(); + } + + private static async Task LoadGame() + { + var slots = _saveManager.ListSlots(); + if (slots.Count == 0) + { + _renderer.ShowMessage(_loc.Get("save.no_saves")); + _renderer.WaitForKeyPress(_loc.Get("prompt.press_key")); + return; + } + + var options = slots.Select(s => $"{s.Name} ({s.SavedAt:yyyy-MM-dd HH:mm})").ToList(); + options.Add(_loc.Get("menu.back")); + + int choice = _renderer.ShowSelection(_loc.Get("save.choose_slot"), options); + if (choice >= slots.Count) return; + + var loaded = _saveManager.Load(slots[choice].Name); + if (loaded == null) + { + _renderer.ShowError("Failed to load save."); + _renderer.WaitForKeyPress(_loc.Get("prompt.press_key")); + return; + } + + _state = loaded; + _loc.Change(_state.CurrentLocale); + InitializeGame(); + + _renderer.ShowMessage(_loc.Get("misc.welcome_back", _state.PlayerName)); + _renderer.WaitForKeyPress(_loc.Get("prompt.press_key")); + + await GameLoop(); + } + + private static void InitializeGame() + { + _registry = ContentRegistry.LoadFromFiles( + "content/data/items.json", + "content/data/boxes.json", + "content/data/interactions.json" + ); + _simulation = new GameSimulation(_registry); + _renderContext = RenderContext.FromGameState(_state); + _renderer = RendererFactory.Create(_renderContext); + } + + private static void ChangeLanguage() + { + var options = new List { "English", "Francais" }; + int choice = _renderer.ShowSelection(_loc.Get("menu.language"), options); + + var newLocale = choice == 0 ? Locale.EN : Locale.FR; + _loc.Change(newLocale); + + if (_state != null) + _state.CurrentLocale = newLocale; + + _renderer = RendererFactory.Create(_renderContext); + } + + private static async Task GameLoop() + { + while (_running) + { + _renderer.Clear(); + _renderer.ShowGameState(_state, _renderContext); + + var actions = BuildActionList(); + if (actions.Count == 0) + { + _renderer.ShowMessage(_loc.Get("error.no_boxes")); + _renderer.WaitForKeyPress(_loc.Get("prompt.press_key")); + break; + } + + int choice = _renderer.ShowSelection( + _loc.Get("prompt.choose_action"), + actions.Select(a => a.label).ToList()); + + await ExecuteAction(actions[choice].action); + } + } + + private static List<(string label, string action)> BuildActionList() + { + var actions = new List<(string label, string action)>(); + + var boxes = _state.Inventory.Where(i => _registry.IsBox(i.DefinitionId)).ToList(); + if (boxes.Count > 0) + actions.Add((_loc.Get("action.open_box") + $" ({boxes.Count})", "open_box")); + + if (_state.Inventory.Count > 0) + actions.Add((_loc.Get("action.inventory"), "inventory")); + + if (_state.UnlockedAdventures.Count > 0) + actions.Add((_loc.Get("action.adventure"), "adventure")); + + if (_state.UnlockedCosmetics.Count > 0) + actions.Add((_loc.Get("action.appearance"), "appearance")); + + actions.Add((_loc.Get("action.save"), "save")); + actions.Add((_loc.Get("action.quit"), "quit")); + + return actions; + } + + private static async Task ExecuteAction(string action) + { + switch (action) + { + case "open_box": await OpenBoxAction(); break; + case "inventory": ShowInventory(); break; + case "adventure": await StartAdventure(); break; + case "appearance": ChangeAppearance(); break; + case "save": SaveGame(); break; + case "quit": _running = false; break; + } + } + + private static async Task OpenBoxAction() + { + var boxes = _state.Inventory.Where(i => _registry.IsBox(i.DefinitionId)).ToList(); + if (boxes.Count == 0) + { + _renderer.ShowMessage(_loc.Get("box.no_boxes")); + return; + } + + var boxNames = boxes.Select(b => + _loc.Get(_registry.GetItem(b.DefinitionId)?.NameKey ?? b.DefinitionId)).ToList(); + boxNames.Add(_loc.Get("menu.back")); + + int choice = _renderer.ShowSelection(_loc.Get("prompt.choose_box"), boxNames); + if (choice >= boxes.Count) return; + + var boxInstance = boxes[choice]; + + var openAction = new OpenBoxAction(boxInstance.Id) + { + BoxDefinitionId = boxInstance.DefinitionId + }; + var events = _simulation.ProcessAction(openAction, _state); + + await RenderEvents(events); + } + + private static async Task RenderEvents(List events) + { + foreach (var evt in events) + { + switch (evt) + { + case BoxOpenedEvent boxEvt: + var boxDef = _registry.GetBox(boxEvt.BoxId); + _renderer.ShowBoxOpening( + _loc.Get(boxDef?.NameKey ?? boxEvt.BoxId), + boxDef?.Rarity.ToString() ?? "Common"); + break; + + case ItemReceivedEvent itemEvt: + _state.AddItem(itemEvt.Item); + var itemDef = _registry.GetItem(itemEvt.Item.DefinitionId); + _renderer.ShowLootReveal( + [ + ( + _loc.Get(itemDef?.NameKey ?? itemEvt.Item.DefinitionId), + (itemDef?.Rarity ?? ItemRarity.Common).ToString(), + (itemDef?.Category ?? ItemCategory.Box).ToString() + ) + ]); + break; + + case UIFeatureUnlockedEvent uiEvt: + _renderContext.Unlock(uiEvt.Feature); + _renderer = RendererFactory.Create(_renderContext); + var featureKey = $"meta.{uiEvt.Feature.ToString().ToLower()}"; + _renderer.ShowUIFeatureUnlocked( + _loc.Get("meta.unlocked", _loc.Get(featureKey))); + _renderer.WaitForKeyPress(_loc.Get("prompt.press_key")); + break; + + case InteractionTriggeredEvent interEvt: + _renderer.ShowInteraction(_loc.Get(interEvt.DescriptionKey)); + break; + + case ResourceChangedEvent resEvt: + var resName = _loc.Get($"resource.{resEvt.Type.ToString().ToLower()}"); + _renderer.ShowMessage($"{resName}: {resEvt.OldValue} -> {resEvt.NewValue}"); + break; + + case MessageEvent msgEvt: + _renderer.ShowMessage(_loc.Get(msgEvt.MessageKey, msgEvt.Args ?? [])); + break; + + case ChoiceRequiredEvent choiceEvt: + _renderer.ShowSelection(_loc.Get(choiceEvt.Prompt), choiceEvt.Options); + break; + + case LootTableModifiedEvent: + _renderer.ShowMessage(_loc.Get("interaction.key_no_match")); + break; + + case AdventureStartedEvent advEvt: + await RunAdventure(advEvt.Theme); + break; + } + } + + _renderer.WaitForKeyPress(_loc.Get("prompt.press_key")); + } + + private static void ShowInventory() + { + _renderer.Clear(); + if (_state.Inventory.Count == 0) + { + _renderer.ShowMessage("Your inventory is empty. Open more boxes!"); + _renderer.WaitForKeyPress(_loc.Get("prompt.press_key")); + return; + } + + var grouped = _state.Inventory + .GroupBy(i => i.DefinitionId) + .Select(g => + { + var def = _registry.GetItem(g.Key); + return ( + name: _loc.Get(def?.NameKey ?? g.Key), + rarity: (def?.Rarity ?? ItemRarity.Common).ToString(), + category: (def?.Category ?? ItemCategory.Box).ToString() + ); + }) + .OrderBy(i => i.category) + .ThenBy(i => i.name) + .ToList(); + + _renderer.ShowLootReveal(grouped); + _renderer.WaitForKeyPress(_loc.Get("prompt.press_key")); + } + + private static async Task StartAdventure() + { + var available = _state.UnlockedAdventures.ToList(); + if (available.Count == 0) + { + _renderer.ShowMessage("No adventures available yet. Keep opening boxes!"); + _renderer.WaitForKeyPress(_loc.Get("prompt.press_key")); + return; + } + + var options = available.Select(a => + { + bool completed = _state.CompletedAdventures.Contains(a.ToString()); + return (completed ? "[Done] " : "") + a.ToString(); + }).ToList(); + options.Add(_loc.Get("menu.back")); + + int choice = _renderer.ShowSelection(_loc.Get("action.adventure"), options); + if (choice >= available.Count) return; + + await RunAdventure(available[choice]); + } + + private static async Task RunAdventure(AdventureTheme theme) + { + try + { + var adventureEngine = new AdventureEngine(_renderer, _loc); + var events = await adventureEngine.PlayAdventure(theme, _state); + + foreach (var evt in events) + { + if (evt.Kind == GameEventKind.ItemGranted) + _state.AddItem(ItemInstance.Create(evt.TargetId, evt.Amount)); + } + + _renderer.ShowMessage(_loc.Get("adventure.completed")); + } + catch (FileNotFoundException) + { + _renderer.ShowMessage($"Adventure '{theme}' is coming soon! The boxes are still being assembled."); + } + catch (Exception ex) + { + _renderer.ShowError($"Adventure error: {ex.Message}"); + } + _renderer.WaitForKeyPress(_loc.Get("prompt.press_key")); + } + + private static void ChangeAppearance() + { + var cosmeticItems = _state.Inventory + .Where(i => + { + var def = _registry.GetItem(i.DefinitionId); + return def?.Category == ItemCategory.Cosmetic && def.CosmeticSlot.HasValue; + }) + .ToList(); + + if (cosmeticItems.Count == 0) + { + _renderer.ShowMessage("No cosmetics available yet. Open Style Boxes!"); + _renderer.WaitForKeyPress(_loc.Get("prompt.press_key")); + return; + } + + var options = cosmeticItems.Select(i => + { + var def = _registry.GetItem(i.DefinitionId); + return $"[{def?.CosmeticSlot}] {_loc.Get(def?.NameKey ?? i.DefinitionId)}"; + }).ToList(); + options.Add(_loc.Get("menu.back")); + + int choice = _renderer.ShowSelection(_loc.Get("action.appearance"), options); + if (choice >= cosmeticItems.Count) return; + + var action = new EquipCosmeticAction(cosmeticItems[choice].Id); + var events = _simulation.ProcessAction(action, _state); + foreach (var evt in events) + { + if (evt is CosmeticEquippedEvent cosEvt) + _renderer.ShowMessage($"Equipped {cosEvt.Slot}: {cosEvt.NewValue}"); + else if (evt is MessageEvent msgEvt) + _renderer.ShowMessage(_loc.Get(msgEvt.MessageKey, msgEvt.Args ?? [])); + } + _renderer.WaitForKeyPress(_loc.Get("prompt.press_key")); + } + + private static void SaveGame() + { + _renderer.ShowMessage(_loc.Get("save.saving")); + _saveManager.Save(_state, _state.PlayerName); + _renderer.ShowMessage(_loc.Get("save.saved", _state.PlayerName)); + _renderer.WaitForKeyPress(_loc.Get("prompt.press_key")); + } +} diff --git a/src/OpenTheBox/Rendering/BasicRenderer.cs b/src/OpenTheBox/Rendering/BasicRenderer.cs new file mode 100644 index 0000000..7568f7c --- /dev/null +++ b/src/OpenTheBox/Rendering/BasicRenderer.cs @@ -0,0 +1,124 @@ +using OpenTheBox.Core; + +namespace OpenTheBox.Rendering; + +/// +/// Phase 0 renderer. Pure Console.WriteLine and Console.ReadLine. +/// No colors, no frames, no fancy stuff. This is the "stone age" of the UI, +/// deliberately ugly and minimal. +/// +public sealed class BasicRenderer : IRenderer +{ + public void ShowMessage(string message) + { + Console.WriteLine(message); + } + + public void ShowError(string message) + { + Console.WriteLine($"ERROR: {message}"); + } + + public void ShowBoxOpening(string boxName, string rarity) + { + Console.WriteLine($"Opening {boxName}..."); + Console.WriteLine("..."); + Console.WriteLine("......"); + Console.WriteLine($"Box opened! (Rarity: {rarity})"); + } + + public void ShowLootReveal(List<(string name, string rarity, string category)> items) + { + Console.WriteLine("You received:"); + for (int i = 0; i < items.Count; i++) + { + var (name, rarity, category) = items[i]; + Console.WriteLine($" - {name} [{rarity}] ({category})"); + } + } + + public int ShowSelection(string prompt, List options) + { + Console.WriteLine(prompt); + for (int i = 0; i < options.Count; i++) + { + Console.WriteLine($" {i + 1}. {options[i]}"); + } + + while (true) + { + Console.Write("> "); + string? input = Console.ReadLine(); + if (int.TryParse(input, out int choice) && choice >= 1 && choice <= options.Count) + { + return choice - 1; + } + Console.WriteLine($"Please enter a number between 1 and {options.Count}."); + } + } + + public string ShowTextInput(string prompt) + { + Console.Write($"{prompt}: "); + return Console.ReadLine() ?? string.Empty; + } + + public void ShowGameState(GameState state, RenderContext context) + { + // Phase 0: no panels unlocked yet, so nothing to show. + } + + public void ShowAdventureDialogue(string? character, string text) + { + if (character is not null) + { + Console.WriteLine($"[{character}]"); + } + Console.WriteLine(text); + Console.WriteLine(); + } + + public int ShowAdventureChoice(List options) + { + Console.WriteLine("What do you do?"); + for (int i = 0; i < options.Count; i++) + { + Console.WriteLine($" {i + 1}. {options[i]}"); + } + + while (true) + { + Console.Write("> "); + string? input = Console.ReadLine(); + if (int.TryParse(input, out int choice) && choice >= 1 && choice <= options.Count) + { + return choice - 1; + } + Console.WriteLine($"Please enter a number between 1 and {options.Count}."); + } + } + + public void ShowUIFeatureUnlocked(string featureName) + { + Console.WriteLine("========================================"); + Console.WriteLine($" NEW FEATURE UNLOCKED: {featureName}"); + Console.WriteLine("========================================"); + } + + public void ShowInteraction(string description) + { + Console.WriteLine($"* {description} *"); + } + + public void WaitForKeyPress(string? message = null) + { + Console.WriteLine(message ?? "Press any key to continue..."); + Console.ReadKey(intercept: true); + Console.WriteLine(); + } + + public void Clear() + { + Console.Clear(); + } +} diff --git a/src/OpenTheBox/Rendering/IRenderer.cs b/src/OpenTheBox/Rendering/IRenderer.cs new file mode 100644 index 0000000..dcec87b --- /dev/null +++ b/src/OpenTheBox/Rendering/IRenderer.cs @@ -0,0 +1,75 @@ +using OpenTheBox.Core; + +namespace OpenTheBox.Rendering; + +/// +/// Interface for all rendering operations. The game loop calls this to display things. +/// Implementations range from plain-text console output to rich Spectre.Console UI. +/// +public interface IRenderer +{ + /// + /// Displays a general-purpose message to the player. + /// + void ShowMessage(string message); + + /// + /// Displays an error message, typically in a distinct style. + /// + void ShowError(string message); + + /// + /// Shows the box-opening sequence for the given box name and rarity. + /// + void ShowBoxOpening(string boxName, string rarity); + + /// + /// Reveals the loot obtained from a box, listing each item with its name, rarity, and category. + /// + void ShowLootReveal(List<(string name, string rarity, string category)> items); + + /// + /// Presents a selection prompt and returns the zero-based index chosen by the player. + /// + int ShowSelection(string prompt, List options); + + /// + /// Prompts the player for free-form text input and returns the entered string. + /// + string ShowTextInput(string prompt); + + /// + /// Renders the current game state using the given render context to decide which panels to show. + /// + void ShowGameState(GameState state, RenderContext context); + + /// + /// Displays a line of adventure dialogue, optionally attributed to a character. + /// + void ShowAdventureDialogue(string? character, string text); + + /// + /// Presents adventure choices and returns the zero-based index chosen by the player. + /// + int ShowAdventureChoice(List options); + + /// + /// Announces that a new UI feature has been unlocked. + /// + void ShowUIFeatureUnlocked(string featureName); + + /// + /// Displays an interaction description to the player. + /// + void ShowInteraction(string description); + + /// + /// Waits for the player to press any key before continuing. + /// + void WaitForKeyPress(string? message = null); + + /// + /// Clears the screen. + /// + void Clear(); +} diff --git a/src/OpenTheBox/Rendering/Panels/ChatPanel.cs b/src/OpenTheBox/Rendering/Panels/ChatPanel.cs new file mode 100644 index 0000000..25e623a --- /dev/null +++ b/src/OpenTheBox/Rendering/Panels/ChatPanel.cs @@ -0,0 +1,61 @@ +using Spectre.Console; +using Spectre.Console.Rendering; + +namespace OpenTheBox.Rendering.Panels; + +/// +/// Renders recent adventure dialogue messages in a framed panel. +/// +public static class ChatPanel +{ + private const int MaxVisibleMessages = 10; + + /// + /// Builds a renderable chat log from a list of dialogue messages. + /// Each message has an optional character name and text content. + /// + public static IRenderable Render(List<(string? character, string text)> messages) + { + var rows = new List(); + + // Show only the most recent messages + var visible = messages.Count > MaxVisibleMessages + ? messages.Skip(messages.Count - MaxVisibleMessages).ToList() + : messages; + + if (visible.Count == 0) + { + rows.Add(new Markup("[dim]No dialogue yet.[/]")); + } + else + { + foreach (var (character, text) in visible) + { + if (character is not null) + { + string color = CharacterColor(character); + rows.Add(new Markup($"[bold {color}]{Markup.Escape(character)}:[/] {Markup.Escape(text)}")); + } + else + { + rows.Add(new Markup($"[italic dim]{Markup.Escape(text)}[/]")); + } + } + } + + return new Panel(new Rows(rows)) + .Header("[bold aqua]Chat[/]") + .Border(BoxBorder.Rounded); + } + + /// + /// Assigns a consistent color to a character name so each speaker is visually distinct. + /// + private static string CharacterColor(string character) + { + // Simple hash-based color selection for consistent per-character coloring + string[] colors = ["aqua", "yellow", "green", "magenta", "orange1", "cyan1", "deeppink1", "chartreuse1"]; + int hash = Math.Abs(character.GetHashCode()); + return colors[hash % colors.Length]; + } +} diff --git a/src/OpenTheBox/Rendering/Panels/InventoryPanel.cs b/src/OpenTheBox/Rendering/Panels/InventoryPanel.cs new file mode 100644 index 0000000..68b8ea1 --- /dev/null +++ b/src/OpenTheBox/Rendering/Panels/InventoryPanel.cs @@ -0,0 +1,65 @@ +using OpenTheBox.Core; +using OpenTheBox.Core.Enums; +using OpenTheBox.Core.Items; +using OpenTheBox.Localization; +using Spectre.Console; +using Spectre.Console.Rendering; + +namespace OpenTheBox.Rendering.Panels; + +/// +/// Renders the player inventory as a Spectre grouped by category. +/// +public static class InventoryPanel +{ + /// + /// Builds a renderable inventory table from the current game state. + /// Uses the localization manager to resolve item name keys. + /// + public static IRenderable Render(GameState state, LocalizationManager? loc = null) + { + var table = new Table() + .Border(TableBorder.Rounded) + .Title("[bold yellow]Inventory[/]") + .AddColumn(new TableColumn("[bold]Name[/]")) + .AddColumn(new TableColumn("[bold]Category[/]").Centered()) + .AddColumn(new TableColumn("[bold]Rarity[/]").Centered()) + .AddColumn(new TableColumn("[bold]Qty[/]").RightAligned()); + + // Group items by their definition id and display grouped + var grouped = state.Inventory + .GroupBy(i => i.DefinitionId) + .OrderBy(g => g.Key); + + foreach (var group in grouped) + { + string defId = group.Key; + int totalQty = group.Sum(i => i.Quantity); + + // Use localization if available, otherwise fall back to definition id + string name = loc is not null ? loc.Get(defId) : defId; + + // We display the definition id as a stand-in for category/rarity since + // we only have ItemInstance at runtime. The full lookup would go through + // an item registry; for now show the raw id. + string category = "-"; + string rarity = "-"; + string color = "white"; + + table.AddRow( + $"[{color}]{Markup.Escape(name)}[/]", + Markup.Escape(category), + $"[{color}]{Markup.Escape(rarity)}[/]", + totalQty.ToString()); + } + + if (!state.Inventory.Any()) + { + table.AddRow("[dim]Empty[/]", "", "", ""); + } + + return new Panel(table) + .Header("[bold yellow]Inventory[/]") + .Border(BoxBorder.Rounded); + } +} diff --git a/src/OpenTheBox/Rendering/Panels/PortraitPanel.cs b/src/OpenTheBox/Rendering/Panels/PortraitPanel.cs new file mode 100644 index 0000000..f6de77e --- /dev/null +++ b/src/OpenTheBox/Rendering/Panels/PortraitPanel.cs @@ -0,0 +1,134 @@ +using OpenTheBox.Core.Characters; +using OpenTheBox.Core.Enums; +using Spectre.Console; +using Spectre.Console.Rendering; + +namespace OpenTheBox.Rendering.Panels; + +/// +/// Generates simple ASCII art for the player character based on equipped cosmetics. +/// Different styles change individual pieces of the portrait. +/// +public static class PortraitPanel +{ + /// + /// Builds a renderable ASCII art portrait from the player's appearance settings. + /// + public static IRenderable Render(PlayerAppearance appearance) + { + string hair = GetHairArt(appearance.HairStyle); + string eyes = GetEyeArt(appearance.EyeStyle); + string body = GetBodyArt(appearance.BodyStyle); + string legs = GetLegArt(appearance.LegStyle); + string arms = GetArmArt(appearance.ArmStyle); + + string hairColor = TintToColor(appearance.HairTint); + string bodyColor = TintToColor(appearance.BodyTint); + + string portrait = string.Join(Environment.NewLine, + $"[{hairColor}]{Markup.Escape(hair)}[/]", + $"[white]{Markup.Escape(eyes)}[/]", + $"[{bodyColor}]{Markup.Escape(body)}[/]", + $"[{bodyColor}]{Markup.Escape(legs)}[/]", + $"[{bodyColor}]{Markup.Escape(arms)}[/]"); + + return new Panel(new Markup(portrait)) + .Header("[bold green]Portrait[/]") + .Border(BoxBorder.Rounded) + .Padding(1, 0); + } + + // ── Hair styles ───────────────────────────────────────────────────── + + private static string GetHairArt(HairStyle style) => style switch + { + HairStyle.None => " .-. ", + HairStyle.Short => " ~~~ ", + HairStyle.Long => " ~~~~~ ", + HairStyle.Ponytail => " ~~~\\ ", + HairStyle.Braided => " ///\\\\\\ ", + HairStyle.Cyberpunk => " /\\/\\/\\ ", + HairStyle.Fire => " ||| ", + HairStyle.StardustLegendary => " @@@@@ ", + _ => " ??? " + }; + + // ── Eye styles ────────────────────────────────────────────────────── + + private static string GetEyeArt(EyeStyle style) => style switch + { + EyeStyle.None => " ( o.o ) ", + EyeStyle.Blue => " ( O.O ) ", + EyeStyle.Green => " ( -._ ) ", + EyeStyle.RedOrange => " ( >.< ) ", + EyeStyle.Brown => " ( ^.o ) ", + EyeStyle.Black => " ( -.- ) ", + EyeStyle.Sunglasses => " ( B-) ) ", + EyeStyle.PilotGlasses => " ( B-) ) ", + EyeStyle.AircraftGlasses => " ( B-) ) ", + EyeStyle.CyberneticEyes => " ( *.* ) ", + EyeStyle.MagicianGlasses => " ( o.o ) ", + _ => " ( ?.? ) " + }; + + // ── Body styles ───────────────────────────────────────────────────── + + private static string GetBodyArt(BodyStyle style) => style switch + { + BodyStyle.Naked => " |[---]| ", + BodyStyle.RegularTShirt => " |[===]| ", + BodyStyle.SexyTShirt => " |[~~~]| ", + BodyStyle.Suit => " |[###]| ", + BodyStyle.Armored => " |{===}| ", + BodyStyle.Robotic => " \\(===)/ ", + _ => " |[???]| " + }; + + // ── Leg styles ────────────────────────────────────────────────────── + + private static string GetLegArt(LegStyle style) => style switch + { + LegStyle.None => " | | ", + LegStyle.Naked => " | | ", + LegStyle.Slip => " | | ", + LegStyle.Short => " | | ", + LegStyle.Panty => " | | ", + LegStyle.RocketBoots => " [| |] ", + LegStyle.PegLeg => " |/ ", + LegStyle.Tentacles => " {| |} ", + _ => " | | " + }; + + // ── Arm styles ────────────────────────────────────────────────────── + + private static string GetArmArt(ArmStyle style) => style switch + { + ArmStyle.None => " / \\ ", + ArmStyle.Short => " / \\ ", + ArmStyle.Regular => " _/ \\_ ", + ArmStyle.Long => " X X ", + ArmStyle.Mechanical => " / ~ ", + ArmStyle.Wings => " ", + ArmStyle.ExtraPair => "
", + _ => " / \\ " + }; + + // ── Tint mapping ──────────────────────────────────────────────────── + + private static string TintToColor(TintColor tint) => tint switch + { + TintColor.None => "white", + TintColor.Cyan => "aqua", + TintColor.Orange => "orange1", + TintColor.Purple => "purple", + TintColor.WarmPink => "deeppink1", + TintColor.Light => "white", + TintColor.Dark => "grey", + TintColor.Rainbow => "gold1", + TintColor.Neon => "green", + TintColor.Silver => "silver", + TintColor.Gold => "gold1", + TintColor.Void => "grey", + _ => "white" + }; +} diff --git a/src/OpenTheBox/Rendering/Panels/ResourcePanel.cs b/src/OpenTheBox/Rendering/Panels/ResourcePanel.cs new file mode 100644 index 0000000..e32596f --- /dev/null +++ b/src/OpenTheBox/Rendering/Panels/ResourcePanel.cs @@ -0,0 +1,60 @@ +using OpenTheBox.Core; +using OpenTheBox.Core.Enums; +using Spectre.Console; +using Spectre.Console.Rendering; + +namespace OpenTheBox.Rendering.Panels; + +/// +/// Renders visible player resources as horizontal bars showing current / max values. +/// +public static class ResourcePanel +{ + /// + /// Builds a renderable resource display from the current game state. + /// Only resources present in are shown. + /// + public static IRenderable Render(GameState state) + { + var rows = new List(); + + foreach (var resourceType in state.VisibleResources.OrderBy(r => r.ToString())) + { + if (!state.Resources.TryGetValue(resourceType, out var resource)) + continue; + + string label = resourceType.ToString(); + int current = resource.Current; + int max = resource.Max; + + // Build a text-based bar: [####----] 40/100 + int barWidth = 20; + int filled = max > 0 ? (int)Math.Round((double)current / max * barWidth) : 0; + filled = Math.Clamp(filled, 0, barWidth); + int empty = barWidth - filled; + + string bar = new string('#', filled) + new string('-', empty); + string color = GetResourceColor(resourceType); + + rows.Add(new Markup($" [{color}]{Markup.Escape(label)}[/]: [{color}][{bar}][/] {current}/{max}")); + } + + if (rows.Count == 0) + { + rows.Add(new Markup("[dim]No resources visible yet.[/]")); + } + + return new Panel(new Rows(rows)) + .Header("[bold cyan]Resources[/]") + .Border(BoxBorder.Rounded); + } + + private static string GetResourceColor(ResourceType type) => type.ToString().ToLowerInvariant() switch + { + "gold" or "coins" => "gold1", + "energy" or "stamina" => "green", + "mana" or "magic" => "blue", + "health" or "hp" => "red", + _ => "silver" + }; +} diff --git a/src/OpenTheBox/Rendering/Panels/StatsPanel.cs b/src/OpenTheBox/Rendering/Panels/StatsPanel.cs new file mode 100644 index 0000000..de9d24d --- /dev/null +++ b/src/OpenTheBox/Rendering/Panels/StatsPanel.cs @@ -0,0 +1,54 @@ +using OpenTheBox.Core; +using OpenTheBox.Core.Enums; +using Spectre.Console; +using Spectre.Console.Rendering; + +namespace OpenTheBox.Rendering.Panels; + +/// +/// Renders the player stat values in a framed panel. +/// +public static class StatsPanel +{ + /// + /// Builds a renderable stats display from the current game state. + /// Only stats present in are shown. + /// + public static IRenderable Render(GameState state) + { + var rows = new List(); + + foreach (var statType in state.VisibleStats.OrderBy(s => s.ToString())) + { + if (!state.Stats.TryGetValue(statType, out int value)) + continue; + + string label = statType.ToString(); + string color = GetStatColor(statType); + + rows.Add(new Markup($" [{color}]{Markup.Escape(label)}:[/] [bold]{value}[/]")); + } + + if (rows.Count == 0) + { + rows.Add(new Markup("[dim]No stats visible yet.[/]")); + } + + // Add total boxes opened as a bonus stat + rows.Add(new Markup($" [silver]Boxes Opened:[/] [bold]{state.TotalBoxesOpened}[/]")); + + return new Panel(new Rows(rows)) + .Header("[bold magenta]Stats[/]") + .Border(BoxBorder.Rounded); + } + + private static string GetStatColor(StatType type) => type.ToString().ToLowerInvariant() switch + { + "strength" or "power" => "red", + "defense" or "armor" => "blue", + "speed" or "agility" => "green", + "luck" => "gold1", + "intelligence" or "wisdom" => "purple", + _ => "silver" + }; +} diff --git a/src/OpenTheBox/Rendering/RenderContext.cs b/src/OpenTheBox/Rendering/RenderContext.cs new file mode 100644 index 0000000..69ef735 --- /dev/null +++ b/src/OpenTheBox/Rendering/RenderContext.cs @@ -0,0 +1,53 @@ +using OpenTheBox.Core; +using OpenTheBox.Core.Enums; + +namespace OpenTheBox.Rendering; + +/// +/// Tracks which UI features are unlocked. Used by SpectreRenderer to decide what to show. +/// Built from the current so the renderer can progressively +/// enable richer visuals as the player unlocks new features. +/// +public sealed class RenderContext +{ + private readonly HashSet _unlocked = []; + + /// + /// Returns true if the given UI feature is unlocked in this context. + /// + public bool Has(UIFeature feature) => _unlocked.Contains(feature); + + /// + /// Unlocks a UI feature so the renderer can start using it. + /// + public void Unlock(UIFeature feature) => _unlocked.Add(feature); + + // ── Convenience properties ────────────────────────────────────────── + + public bool HasColors => Has(UIFeature.TextColors); + public bool HasExtendedColors => Has(UIFeature.ExtendedColors); + public bool HasArrowSelection => Has(UIFeature.ArrowKeySelection); + public bool HasInventoryPanel => Has(UIFeature.InventoryPanel); + public bool HasResourcePanel => Has(UIFeature.ResourcePanel); + public bool HasStatsPanel => Has(UIFeature.StatsPanel); + public bool HasPortraitPanel => Has(UIFeature.PortraitPanel); + public bool HasChatPanel => Has(UIFeature.ChatPanel); + public bool HasFullLayout => Has(UIFeature.FullLayout); + public bool HasKeyboardShortcuts => Has(UIFeature.KeyboardShortcuts); + public bool HasBoxAnimation => Has(UIFeature.BoxAnimation); + public bool HasCraftingPanel => Has(UIFeature.CraftingPanel); + + /// + /// Builds a that mirrors the features already unlocked in a + /// . + /// + public static RenderContext FromGameState(GameState state) + { + var ctx = new RenderContext(); + foreach (var feature in state.UnlockedUIFeatures) + { + ctx.Unlock(feature); + } + return ctx; + } +} diff --git a/src/OpenTheBox/Rendering/RendererFactory.cs b/src/OpenTheBox/Rendering/RendererFactory.cs new file mode 100644 index 0000000..5f5d636 --- /dev/null +++ b/src/OpenTheBox/Rendering/RendererFactory.cs @@ -0,0 +1,37 @@ +namespace OpenTheBox.Rendering; + +/// +/// Static factory that selects the appropriate implementation +/// based on which UI features the player has unlocked. +/// +public static class RendererFactory +{ + /// + /// Creates an suited to the given context. + /// If the context has any Spectre-capable feature unlocked, a + /// is returned; otherwise the plain is used. + /// + public static IRenderer Create(RenderContext context) + { + bool hasAnySpectreFeature = + context.HasColors || + context.HasExtendedColors || + context.HasArrowSelection || + context.HasInventoryPanel || + context.HasResourcePanel || + context.HasStatsPanel || + context.HasPortraitPanel || + context.HasChatPanel || + context.HasFullLayout || + context.HasKeyboardShortcuts || + context.HasBoxAnimation || + context.HasCraftingPanel; + + if (hasAnySpectreFeature) + { + return new SpectreRenderer(context); + } + + return new BasicRenderer(); + } +} diff --git a/src/OpenTheBox/Rendering/SpectreRenderer.cs b/src/OpenTheBox/Rendering/SpectreRenderer.cs new file mode 100644 index 0000000..722bc09 --- /dev/null +++ b/src/OpenTheBox/Rendering/SpectreRenderer.cs @@ -0,0 +1,411 @@ +using OpenTheBox.Core; +using OpenTheBox.Core.Characters; +using OpenTheBox.Core.Enums; +using OpenTheBox.Rendering.Panels; +using Spectre.Console; + +namespace OpenTheBox.Rendering; + +/// +/// Progressive renderer using Spectre.Console. Checks for each +/// feature and falls back gracefully when a capability is not yet unlocked. +/// +public sealed class SpectreRenderer : IRenderer +{ + // ── Message output ────────────────────────────────────────────────── + + public void ShowMessage(string message) + { + if (_context.HasColors) + { + AnsiConsole.MarkupLine($"[green]{Markup.Escape(message)}[/]"); + } + else + { + Console.WriteLine(message); + } + } + + public void ShowError(string message) + { + if (_context.HasColors) + { + AnsiConsole.MarkupLine($"[bold red]ERROR:[/] [red]{Markup.Escape(message)}[/]"); + } + else + { + Console.WriteLine($"ERROR: {message}"); + } + } + + // ── Box opening ───────────────────────────────────────────────────── + + public void ShowBoxOpening(string boxName, string rarity) + { + if (_context.HasBoxAnimation) + { + string color = RarityColor(rarity); + AnsiConsole.Status() + .Spinner(Spinner.Known.Star) + .SpinnerStyle(new Style(RarityColorValue(rarity))) + .Start($"Opening [bold {color}]{Markup.Escape(boxName)}[/]...", ctx => + { + Thread.Sleep(1500); + ctx.Status($"[bold {color}]Something shimmers...[/]"); + Thread.Sleep(1000); + }); + AnsiConsole.MarkupLine($"[bold {color}]{Markup.Escape(boxName)}[/] opened!"); + } + else if (_context.HasColors) + { + string color = RarityColor(rarity); + AnsiConsole.MarkupLine($"Opening [bold {color}]{Markup.Escape(boxName)}[/]..."); + Thread.Sleep(800); + AnsiConsole.MarkupLine($"[bold {color}]{Markup.Escape(boxName)}[/] opened!"); + } + else + { + Console.WriteLine($"Opening {boxName}..."); + Thread.Sleep(500); + Console.WriteLine($"{boxName} opened! (Rarity: {rarity})"); + } + } + + // ── Loot reveal ───────────────────────────────────────────────────── + + public void ShowLootReveal(List<(string name, string rarity, string category)> items) + { + if (_context.HasInventoryPanel) + { + var table = new Table() + .Border(TableBorder.Rounded) + .Title("[bold yellow]Loot![/]") + .AddColumn(new TableColumn("[bold]Name[/]").Centered()) + .AddColumn(new TableColumn("[bold]Rarity[/]").Centered()) + .AddColumn(new TableColumn("[bold]Category[/]").Centered()); + + foreach (var (name, rarity, category) in items) + { + string color = RarityColor(rarity); + table.AddRow( + $"[{color}]{Markup.Escape(name)}[/]", + $"[{color}]{Markup.Escape(rarity)}[/]", + Markup.Escape(category)); + } + + AnsiConsole.Write(table); + } + else if (_context.HasColors) + { + AnsiConsole.MarkupLine("[bold yellow]You received:[/]"); + foreach (var (name, rarity, category) in items) + { + string color = RarityColor(rarity); + AnsiConsole.MarkupLine($" - [{color}]{Markup.Escape(name)}[/] [{color}][{Markup.Escape(rarity)}][/] ({Markup.Escape(category)})"); + } + } + else + { + Console.WriteLine("You received:"); + foreach (var (name, rarity, category) in items) + { + Console.WriteLine($" - {name} [{rarity}] ({category})"); + } + } + } + + // ── Selection prompts ─────────────────────────────────────────────── + + public int ShowSelection(string prompt, List options) + { + if (_context.HasArrowSelection) + { + string selected = AnsiConsole.Prompt( + new SelectionPrompt() + .Title(Markup.Escape(prompt)) + .PageSize(10) + .AddChoices(options)); + + return options.IndexOf(selected); + } + + if (_context.HasColors) + { + AnsiConsole.MarkupLine($"[bold]{Markup.Escape(prompt)}[/]"); + for (int i = 0; i < options.Count; i++) + { + AnsiConsole.MarkupLine($" [cyan]{i + 1}.[/] {Markup.Escape(options[i])}"); + } + } + else + { + Console.WriteLine(prompt); + for (int i = 0; i < options.Count; i++) + { + Console.WriteLine($" {i + 1}. {options[i]}"); + } + } + + while (true) + { + Console.Write("> "); + string? input = Console.ReadLine(); + if (int.TryParse(input, out int choice) && choice >= 1 && choice <= options.Count) + { + return choice - 1; + } + + if (_context.HasColors) + { + AnsiConsole.MarkupLine($"[red]Please enter a number between 1 and {options.Count}.[/]"); + } + else + { + Console.WriteLine($"Please enter a number between 1 and {options.Count}."); + } + } + } + + public string ShowTextInput(string prompt) + { + if (_context.HasColors) + { + return AnsiConsole.Prompt( + new TextPrompt($"[bold]{Markup.Escape(prompt)}[/]:")); + } + + Console.Write($"{prompt}: "); + return Console.ReadLine() ?? string.Empty; + } + + // ── Game state ────────────────────────────────────────────────────── + + public void ShowGameState(GameState state, RenderContext context) + { + if (context.HasFullLayout) + { + RenderFullLayout(state, context); + } + else + { + RenderSequentialPanels(state, context); + } + } + + // ── Adventure dialogue ────────────────────────────────────────────── + + public void ShowAdventureDialogue(string? character, string text) + { + if (_context.HasColors) + { + if (character is not null) + { + AnsiConsole.MarkupLine($"[bold aqua]{Markup.Escape(character)}[/]"); + } + AnsiConsole.MarkupLine($" [italic]{Markup.Escape(text)}[/]"); + AnsiConsole.WriteLine(); + } + else + { + if (character is not null) + { + Console.WriteLine($"[{character}]"); + } + Console.WriteLine(text); + Console.WriteLine(); + } + } + + public int ShowAdventureChoice(List options) + { + if (_context.HasArrowSelection) + { + string selected = AnsiConsole.Prompt( + new SelectionPrompt() + .Title("[bold yellow]What do you do?[/]") + .PageSize(10) + .AddChoices(options)); + + return options.IndexOf(selected); + } + + return ShowSelection("What do you do?", options); + } + + // ── UI feature unlock announcement ────────────────────────────────── + + public void ShowUIFeatureUnlocked(string featureName) + { + if (_context.HasColors) + { + AnsiConsole.Write(new Rule($"[bold yellow]NEW FEATURE UNLOCKED[/]").RuleStyle("yellow")); + AnsiConsole.Write(new FigletText(featureName).Color(Color.Yellow).Centered()); + AnsiConsole.Write(new Rule().RuleStyle("yellow")); + } + else + { + Console.WriteLine("========================================"); + Console.WriteLine($" NEW FEATURE UNLOCKED: {featureName}"); + Console.WriteLine("========================================"); + } + } + + // ── Interaction ───────────────────────────────────────────────────── + + public void ShowInteraction(string description) + { + if (_context.HasColors) + { + AnsiConsole.MarkupLine($"[italic silver]* {Markup.Escape(description)} *[/]"); + } + else + { + Console.WriteLine($"* {description} *"); + } + } + + // ── Utility ───────────────────────────────────────────────────────── + + public void WaitForKeyPress(string? message = null) + { + if (_context.HasColors) + { + AnsiConsole.MarkupLine($"[dim]{Markup.Escape(message ?? "Press any key to continue...")}[/]"); + } + else + { + Console.WriteLine(message ?? "Press any key to continue..."); + } + + Console.ReadKey(intercept: true); + Console.WriteLine(); + } + + public void Clear() + { + AnsiConsole.Clear(); + } + + // ── Construction ──────────────────────────────────────────────────── + + private RenderContext _context; + + public SpectreRenderer(RenderContext context) + { + _context = context; + } + + /// + /// Allows updating the context when new features are unlocked mid-session. + /// + public void UpdateContext(RenderContext context) + { + _context = context; + } + + // ── Private helpers ───────────────────────────────────────────────── + + private static string RarityColor(string rarity) => rarity.ToLowerInvariant() switch + { + "common" => "white", + "uncommon" => "green", + "rare" => "blue", + "epic" => "purple", + "legendary" => "gold1", + "mythic" => "red", + _ => "white" + }; + + private static Color RarityColorValue(string rarity) => rarity.ToLowerInvariant() switch + { + "common" => Color.White, + "uncommon" => Color.Green, + "rare" => Color.Blue, + "epic" => Color.Purple, + "legendary" => Color.Gold1, + "mythic" => Color.Red, + _ => Color.White + }; + + /// + /// Renders all panels in a full Spectre Layout grid. + /// + private void RenderFullLayout(GameState state, RenderContext context) + { + var layout = new Layout("Root") + .SplitRows( + new Layout("Top") + .SplitColumns( + new Layout("Portrait"), + new Layout("Stats"), + new Layout("Resources")), + new Layout("Middle") + .SplitColumns( + new Layout("Inventory").Ratio(2), + new Layout("Chat")), + new Layout("Bottom")); + + if (context.HasPortraitPanel) + layout["Portrait"].Update(PortraitPanel.Render(state.Appearance)); + else + layout["Portrait"].Update(new Panel("[dim]???[/]").Header("Portrait")); + + if (context.HasStatsPanel) + layout["Stats"].Update(StatsPanel.Render(state)); + else + layout["Stats"].Update(new Panel("[dim]???[/]").Header("Stats")); + + if (context.HasResourcePanel) + layout["Resources"].Update(ResourcePanel.Render(state)); + else + layout["Resources"].Update(new Panel("[dim]???[/]").Header("Resources")); + + if (context.HasInventoryPanel) + layout["Inventory"].Update(InventoryPanel.Render(state)); + else + layout["Inventory"].Update(new Panel("[dim]???[/]").Header("Inventory")); + + if (context.HasChatPanel) + layout["Chat"].Update(ChatPanel.Render([])); + else + layout["Chat"].Update(new Panel("[dim]???[/]").Header("Chat")); + + if (context.HasCraftingPanel) + layout["Bottom"].Update(new Panel("[dim]Crafting area[/]").Header("Crafting")); + else + layout["Bottom"].Update(new Panel("[dim]???[/]").Header("???")); + + AnsiConsole.Write(layout); + } + + /// + /// Renders only the panels the player has unlocked, stacked vertically. + /// + private void RenderSequentialPanels(GameState state, RenderContext context) + { + if (context.HasPortraitPanel) + { + AnsiConsole.Write(PortraitPanel.Render(state.Appearance)); + } + + if (context.HasStatsPanel) + { + AnsiConsole.Write(StatsPanel.Render(state)); + } + + if (context.HasResourcePanel) + { + AnsiConsole.Write(ResourcePanel.Render(state)); + } + + if (context.HasInventoryPanel) + { + AnsiConsole.Write(InventoryPanel.Render(state)); + } + + if (context.HasChatPanel) + { + AnsiConsole.Write(ChatPanel.Render([])); + } + } +} diff --git a/src/OpenTheBox/Simulation/Actions/GameAction.cs b/src/OpenTheBox/Simulation/Actions/GameAction.cs new file mode 100644 index 0000000..e611d54 --- /dev/null +++ b/src/OpenTheBox/Simulation/Actions/GameAction.cs @@ -0,0 +1,52 @@ +using OpenTheBox.Core.Enums; + +namespace OpenTheBox.Simulation.Actions; + +/// +/// Base type for all player-initiated actions in the game. +/// +public abstract record GameAction +{ + public DateTime Timestamp { get; init; } = DateTime.UtcNow; +} + +/// +/// Player opens a box from their inventory. +/// +public sealed record OpenBoxAction(Guid BoxInstanceId) : GameAction +{ + /// + /// The definition id of the box being opened. + /// + public string? BoxDefinitionId { get; init; } +} + +/// +/// Player uses an item from their inventory. +/// +public sealed record UseItemAction(Guid ItemInstanceId) : GameAction; + +/// +/// Player crafts at a workstation using material items. +/// +public sealed record CraftAction(WorkstationType Station, List MaterialIds) : GameAction; + +/// +/// Player starts an adventure. +/// +public sealed record StartAdventureAction(AdventureTheme Theme) : GameAction; + +/// +/// Player changes the game locale. +/// +public sealed record ChangeLocaleAction(Locale NewLocale) : GameAction; + +/// +/// Player equips a cosmetic item. +/// +public sealed record EquipCosmeticAction(Guid ItemInstanceId) : GameAction; + +/// +/// Player saves the game to a named slot. +/// +public sealed record SaveGameAction(string SlotName) : GameAction; diff --git a/src/OpenTheBox/Simulation/BoxEngine.cs b/src/OpenTheBox/Simulation/BoxEngine.cs new file mode 100644 index 0000000..35e6499 --- /dev/null +++ b/src/OpenTheBox/Simulation/BoxEngine.cs @@ -0,0 +1,154 @@ +using OpenTheBox.Core; +using OpenTheBox.Core.Boxes; +using OpenTheBox.Core.Enums; +using OpenTheBox.Core.Items; +using OpenTheBox.Data; +using OpenTheBox.Simulation.Events; + +namespace OpenTheBox.Simulation; + +/// +/// Handles box opening logic: evaluates loot table conditions, performs weighted random rolls, +/// and recursively opens auto-open boxes. +/// +public class BoxEngine(ContentRegistry registry) +{ + /// + /// Opens a box, evaluating its loot table against the current game state and returning + /// all resulting events (items received, nested box opens, etc.). + /// + public List Open(string boxDefId, GameState state, Random rng) + { + var events = new List(); + var boxDef = registry.GetBox(boxDefId); + if (boxDef is null) + return events; + + var eligibleEntries = FilterEligibleEntries(boxDef.LootTable, state); + if (eligibleEntries.Count == 0) + return events; + + var droppedItemDefIds = new List(); + + // Handle guaranteed rolls: take the top entries by weight up to GuaranteedRolls count + var guaranteedCount = Math.Min(boxDef.LootTable.GuaranteedRolls, eligibleEntries.Count); + var guaranteedEntries = eligibleEntries + .OrderByDescending(e => e.Weight) + .Take(guaranteedCount) + .ToList(); + + foreach (var entry in guaranteedEntries) + { + droppedItemDefIds.Add(entry.ItemDefinitionId); + } + + // Handle weighted random rolls + if (boxDef.LootTable.RollCount > 0 && eligibleEntries.Count > 0) + { + var weightedEntries = eligibleEntries + .Select(e => ((LootEntry)e, e.Weight)) + .ToList(); + + var picks = WeightedRandom.PickMultiple(weightedEntries, rng, boxDef.LootTable.RollCount); + foreach (var pick in picks) + { + droppedItemDefIds.Add(pick.ItemDefinitionId); + } + } + + events.Add(new BoxOpenedEvent(boxDefId, droppedItemDefIds)); + + // Create item instances for each dropped item + foreach (var itemDefId in droppedItemDefIds) + { + var itemDef = registry.GetItem(itemDefId); + if (itemDef is null) + continue; + + var instance = ItemInstance.Create(itemDefId); + state.AddItem(instance); + events.Add(new ItemReceivedEvent(instance)); + + // Recursively open auto-open boxes + if (itemDef.Category == ItemCategory.Box) + { + var nestedBoxDef = registry.GetBox(itemDefId); + if (nestedBoxDef is not null && nestedBoxDef.IsAutoOpen) + { + state.RemoveItem(instance.Id); + events.Add(new ItemConsumedEvent(instance.Id)); + events.AddRange(Open(itemDefId, state, rng)); + } + } + } + + return events; + } + + /// + /// Filters loot entries to only those whose conditions are satisfied by the current game state. + /// + private static List FilterEligibleEntries(LootTable lootTable, GameState state) + { + var eligible = new List(); + foreach (var entry in lootTable.Entries) + { + if (entry.Condition is null || EvaluateCondition(entry.Condition, state)) + { + eligible.Add(entry); + } + } + return eligible; + } + + /// + /// Evaluates a single loot condition against the game state. + /// + private static bool EvaluateCondition(LootCondition condition, GameState state) + { + return condition.Type switch + { + LootConditionType.HasItem => condition.TargetId is not null && state.HasItem(condition.TargetId), + LootConditionType.HasNotItem => condition.TargetId is not null && !state.HasItem(condition.TargetId), + LootConditionType.ResourceAbove => condition.TargetId is not null + && condition.Value.HasValue + && Enum.TryParse(condition.TargetId, out var resAbove) + && state.GetResource(resAbove) > condition.Value.Value, + LootConditionType.ResourceBelow => condition.TargetId is not null + && condition.Value.HasValue + && Enum.TryParse(condition.TargetId, out var resBelow) + && state.GetResource(resBelow) < condition.Value.Value, + LootConditionType.HasUIFeature => condition.TargetId is not null + && Enum.TryParse(condition.TargetId, out var feature) + && state.HasUIFeature(feature), + LootConditionType.BoxesOpenedAbove => condition.Value.HasValue + && state.TotalBoxesOpened > condition.Value.Value, + LootConditionType.HasWorkstation => condition.TargetId is not null + && Enum.TryParse(condition.TargetId, out var ws) + && state.UnlockedWorkstations.Contains(ws), + LootConditionType.HasAdventure => condition.TargetId is not null + && Enum.TryParse(condition.TargetId, out var adv) + && state.UnlockedAdventures.Contains(adv), + LootConditionType.HasCosmetic => condition.TargetId is not null + && state.UnlockedCosmetics.Contains(condition.TargetId), + _ => true + }; + } + + /// + /// Performs a comparison operation between an actual value and a target value. + /// + private static bool CompareValue(float actual, string? comparison, float target) + { + return comparison switch + { + ">=" => actual >= target, + "<=" => actual <= target, + ">" => actual > target, + "<" => actual < target, + "==" => Math.Abs(actual - target) < 0.001f, + "!=" => Math.Abs(actual - target) >= 0.001f, + _ => false + }; + } +} diff --git a/src/OpenTheBox/Simulation/Events/GameEvent.cs b/src/OpenTheBox/Simulation/Events/GameEvent.cs new file mode 100644 index 0000000..15b14df --- /dev/null +++ b/src/OpenTheBox/Simulation/Events/GameEvent.cs @@ -0,0 +1,72 @@ +using OpenTheBox.Core.Enums; +using OpenTheBox.Core.Items; + +namespace OpenTheBox.Simulation.Events; + +/// +/// Base type for all events produced by the simulation in response to actions. +/// +public abstract record GameEvent +{ + public DateTime Timestamp { get; init; } = DateTime.UtcNow; +} + +/// +/// A box was opened, producing dropped items. +/// +public sealed record BoxOpenedEvent(string BoxId, List DroppedItemDefIds) : GameEvent; + +/// +/// An item was received and added to inventory. +/// +public sealed record ItemReceivedEvent(ItemInstance Item) : GameEvent; + +/// +/// An item was consumed and removed from inventory. +/// +public sealed record ItemConsumedEvent(Guid InstanceId) : GameEvent; + +/// +/// An interaction rule was triggered. +/// +public sealed record InteractionTriggeredEvent(string RuleId, string DescriptionKey) : GameEvent; + +/// +/// A UI feature was unlocked via a meta item. +/// +public sealed record UIFeatureUnlockedEvent(UIFeature Feature) : GameEvent; + +/// +/// A resource value changed. +/// +public sealed record ResourceChangedEvent(ResourceType Type, int OldValue, int NewValue) : GameEvent; + +/// +/// A cosmetic was equipped in a slot. +/// +public sealed record CosmeticEquippedEvent(CosmeticSlot Slot, string NewValue) : GameEvent; + +/// +/// An adventure was started. +/// +public sealed record AdventureStartedEvent(AdventureTheme Theme) : GameEvent; + +/// +/// An adventure was completed. +/// +public sealed record AdventureCompletedEvent(AdventureTheme Theme) : GameEvent; + +/// +/// A loot table was modified (e.g., a key injected a matching box). +/// +public sealed record LootTableModifiedEvent(string BoxId, string AddedEntryId, string Reason) : GameEvent; + +/// +/// The simulation requires the player to make a choice between options. +/// +public sealed record ChoiceRequiredEvent(string Prompt, List Options) : GameEvent; + +/// +/// A generic message to be displayed to the player. +/// +public sealed record MessageEvent(string MessageKey, string[]? Args = null) : GameEvent; diff --git a/src/OpenTheBox/Simulation/GameSimulation.cs b/src/OpenTheBox/Simulation/GameSimulation.cs new file mode 100644 index 0000000..9fd5675 --- /dev/null +++ b/src/OpenTheBox/Simulation/GameSimulation.cs @@ -0,0 +1,221 @@ +using OpenTheBox.Core; +using OpenTheBox.Core.Enums; +using OpenTheBox.Core.Items; +using OpenTheBox.Data; +using OpenTheBox.Simulation.Actions; +using OpenTheBox.Simulation.Events; + +namespace OpenTheBox.Simulation; + +/// +/// The BLACK BOX. Zero I/O. Central orchestrator for all game logic. +/// All game actions are processed through this class, which dispatches to the appropriate +/// engine and runs post-processing passes (auto-activation, meta unlocks). +/// +public class GameSimulation +{ + private readonly ContentRegistry _registry; + private readonly Random _rng; + private readonly BoxEngine _boxEngine; + private readonly InteractionEngine _interactionEngine; + private readonly MetaEngine _metaEngine; + private readonly ResourceEngine _resourceEngine; + + public GameSimulation(ContentRegistry registry, Random? rng = null) + { + _registry = registry; + _rng = rng ?? new Random(); + _boxEngine = new BoxEngine(registry); + _interactionEngine = new InteractionEngine(registry); + _metaEngine = new MetaEngine(); + _resourceEngine = new ResourceEngine(); + } + + /// + /// Processes a game action against the current state, returning all resulting events in order. + /// This is the single entry point for all game logic. + /// + public List ProcessAction(GameAction action, GameState state) + { + var events = new List(); + + switch (action) + { + case OpenBoxAction openBox: + events.AddRange(HandleOpenBox(openBox, state)); + break; + + case UseItemAction useItem: + events.AddRange(HandleUseItem(useItem, state)); + break; + + case CraftAction craft: + events.AddRange(HandleCraft(craft, state)); + break; + + case StartAdventureAction startAdventure: + events.AddRange(HandleStartAdventure(startAdventure, state)); + break; + + case ChangeLocaleAction changeLocale: + events.AddRange(HandleChangeLocale(changeLocale, state)); + break; + + case EquipCosmeticAction equipCosmetic: + events.AddRange(HandleEquipCosmetic(equipCosmetic, state)); + break; + + case SaveGameAction: + // Save is handled externally; simulation just acknowledges + events.Add(new MessageEvent("save.acknowledged")); + break; + } + + return events; + } + + private List HandleOpenBox(OpenBoxAction action, GameState state) + { + var events = new List(); + + // Find the box item in inventory + var boxItem = state.Inventory.FirstOrDefault(i => i.Id == action.BoxInstanceId); + if (boxItem is null) + { + events.Add(new MessageEvent("error.item_not_found")); + return events; + } + + // Consume the box item + state.RemoveItem(boxItem.Id); + events.Add(new ItemConsumedEvent(boxItem.Id)); + + // Open the box + var boxEvents = _boxEngine.Open(boxItem.DefinitionId, state, _rng); + events.AddRange(boxEvents); + + state.TotalBoxesOpened++; + + // Collect newly received items for post-processing + var newItems = boxEvents.OfType().Select(e => e.Item).ToList(); + + // Run auto-activation pass + events.AddRange(_interactionEngine.CheckAutoActivations(newItems, state)); + + // Run meta pass + events.AddRange(_metaEngine.ProcessNewItems(newItems, state, _registry)); + + return events; + } + + private List HandleUseItem(UseItemAction action, GameState state) + { + var events = new List(); + + var item = state.Inventory.FirstOrDefault(i => i.Id == action.ItemInstanceId); + if (item is null) + { + events.Add(new MessageEvent("error.item_not_found")); + return events; + } + + var itemDef = _registry.GetItem(item.DefinitionId); + if (itemDef is null) + { + events.Add(new MessageEvent("error.definition_not_found")); + return events; + } + + // Check if it's a consumable resource item + if (itemDef.ResourceType.HasValue) + { + events.AddRange(_resourceEngine.ProcessConsumable(item, state, _registry)); + return events; + } + + // Otherwise, check interaction rules + var interactionEvents = _interactionEngine.CheckAutoActivations([item], state); + events.AddRange(interactionEvents); + + return events; + } + + private List HandleCraft(CraftAction action, GameState state) + { + var events = new List(); + + if (!state.UnlockedWorkstations.Contains(action.Station)) + { + events.Add(new MessageEvent("error.workstation_locked")); + return events; + } + + // Consume all material items + foreach (var materialId in action.MaterialIds) + { + var material = state.Inventory.FirstOrDefault(i => i.Id == materialId); + if (material is null) + { + events.Add(new MessageEvent("error.material_not_found", [materialId.ToString()])); + return events; + } + + state.RemoveItem(materialId); + events.Add(new ItemConsumedEvent(materialId)); + } + + // Crafting result is determined by interaction rules matching the materials + // The interaction engine will handle rule matching and result production + events.Add(new MessageEvent("craft.materials_consumed")); + + return events; + } + + private List HandleStartAdventure(StartAdventureAction action, GameState state) + { + var events = new List(); + + if (!state.UnlockedAdventures.Contains(action.Theme)) + { + events.Add(new MessageEvent("error.adventure_locked")); + return events; + } + + events.Add(new AdventureStartedEvent(action.Theme)); + + return events; + } + + private static List HandleChangeLocale(ChangeLocaleAction action, GameState state) + { + var events = new List(); + + state.CurrentLocale = action.NewLocale; + events.Add(new MessageEvent("locale.changed", [action.NewLocale.ToString()])); + + return events; + } + + private List HandleEquipCosmetic(EquipCosmeticAction action, GameState state) + { + var events = new List(); + + var item = state.Inventory.FirstOrDefault(i => i.Id == action.ItemInstanceId); + if (item is null) + { + events.Add(new MessageEvent("error.item_not_found")); + return events; + } + + var itemDef = _registry.GetItem(item.DefinitionId); + if (itemDef?.CosmeticSlot is null || itemDef.CosmeticValue is null) + { + events.Add(new MessageEvent("error.not_a_cosmetic")); + return events; + } + + events.Add(new CosmeticEquippedEvent(itemDef.CosmeticSlot.Value, itemDef.CosmeticValue)); + + return events; + } +} diff --git a/src/OpenTheBox/Simulation/InteractionEngine.cs b/src/OpenTheBox/Simulation/InteractionEngine.cs new file mode 100644 index 0000000..56b2d4e --- /dev/null +++ b/src/OpenTheBox/Simulation/InteractionEngine.cs @@ -0,0 +1,139 @@ +using OpenTheBox.Core; +using OpenTheBox.Core.Enums; +using OpenTheBox.Core.Interactions; +using OpenTheBox.Core.Items; +using OpenTheBox.Data; +using OpenTheBox.Simulation.Events; + +namespace OpenTheBox.Simulation; + +/// +/// Evaluates interaction rules against newly received items and the current game state, +/// triggering automatic interactions or requesting player choices when multiple apply. +/// +public class InteractionEngine(ContentRegistry registry) +{ + /// + /// Checks all interaction rules against newly received items and returns events for + /// any auto-activations or choice prompts. + /// + public List CheckAutoActivations(List newItems, GameState state) + { + var events = new List(); + + foreach (var newItem in newItems) + { + var itemDef = registry.GetItem(newItem.DefinitionId); + if (itemDef is null) + continue; + + var matchingRules = FindMatchingRules(itemDef, state); + + if (matchingRules.Count == 0) + { + // Special case: key without a matching openable item injects a box into future loot tables + if (itemDef.Tags.Contains("Key")) + { + var hasMatchingOpenable = state.Inventory.Any(i => + { + var def = registry.GetItem(i.DefinitionId); + return def is not null && def.Tags.Contains("Openable"); + }); + + if (!hasMatchingOpenable) + { + events.Add(new LootTableModifiedEvent( + BoxId: "starter_box", + AddedEntryId: newItem.DefinitionId, + Reason: $"Key '{newItem.DefinitionId}' has no matching Openable item; injecting into future loot tables" + )); + } + } + + continue; + } + + var automaticRules = matchingRules + .Where(r => r.IsAutomatic) + .OrderByDescending(r => r.Priority) + .ToList(); + + if (automaticRules.Count == 1) + { + // Single automatic match: auto-execute + var rule = automaticRules[0]; + events.AddRange(ExecuteRule(rule, newItem, state)); + } + else if (automaticRules.Count > 1) + { + // Multiple automatic matches: let the player choose + events.Add(new ChoiceRequiredEvent( + Prompt: $"Multiple interactions available for '{newItem.DefinitionId}'", + Options: automaticRules.Select(r => r.Id).ToList() + )); + } + else + { + // Non-automatic rules found but none are automatic -- no auto-activation + } + } + + return events; + } + + /// + /// Finds all interaction rules that match the given item definition and game state. + /// + private List FindMatchingRules(ItemDefinition itemDef, GameState state) + { + var matching = new List(); + + foreach (var rule in registry.InteractionRules) + { + // Check required tags + var tagsMatch = rule.RequiredItemTags.All(tag => itemDef.Tags.Contains(tag)); + if (!tagsMatch) + continue; + + // Check required item ids (if specified, at least one must be in inventory) + if (rule.RequiredItemIds is not null && rule.RequiredItemIds.Count > 0) + { + var hasRequiredItem = rule.RequiredItemIds.All(id => state.HasItem(id)); + if (!hasRequiredItem) + continue; + } + + matching.Add(rule); + } + + return matching; + } + + /// + /// Executes a single interaction rule, consuming the trigger item and producing results. + /// + private List ExecuteRule(InteractionRule rule, ItemInstance triggerItem, GameState state) + { + var events = new List(); + + // Consume the trigger item + state.RemoveItem(triggerItem.Id); + events.Add(new ItemConsumedEvent(triggerItem.Id)); + + // Produce result items if ResultData specifies item definition ids (comma-separated) + if (rule.ResultData is not null) + { + var resultItemIds = rule.ResultData.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + foreach (var resultItemId in resultItemIds) + { + var resultInstance = ItemInstance.Create(resultItemId); + state.AddItem(resultInstance); + events.Add(new ItemReceivedEvent(resultInstance)); + } + } + + events.Add(new InteractionTriggeredEvent(rule.Id, rule.DescriptionKey)); + + return events; + } +} diff --git a/src/OpenTheBox/Simulation/MetaEngine.cs b/src/OpenTheBox/Simulation/MetaEngine.cs new file mode 100644 index 0000000..bb5c611 --- /dev/null +++ b/src/OpenTheBox/Simulation/MetaEngine.cs @@ -0,0 +1,61 @@ +using OpenTheBox.Core; +using OpenTheBox.Core.Enums; +using OpenTheBox.Core.Items; +using OpenTheBox.Data; +using OpenTheBox.Simulation.Events; + +namespace OpenTheBox.Simulation; + +/// +/// Processes meta-tagged items to unlock UI features, make resources/stats visible, +/// and apply cosmetic or other permanent progression changes. +/// +public class MetaEngine +{ + /// + /// Processes newly received items and applies any meta-unlock effects to the game state. + /// + public List ProcessNewItems(List newItems, GameState state, ContentRegistry registry) + { + var events = new List(); + + foreach (var item in newItems) + { + var itemDef = registry.GetItem(item.DefinitionId); + if (itemDef is null) + continue; + + // Unlock UI feature if this item has a MetaUnlock + if (itemDef.MetaUnlock.HasValue && state.UnlockedUIFeatures.Add(itemDef.MetaUnlock.Value)) + { + events.Add(new UIFeatureUnlockedEvent(itemDef.MetaUnlock.Value)); + } + + // Make resource type visible if this item references a resource + if (itemDef.ResourceType.HasValue) + { + state.VisibleResources.Add(itemDef.ResourceType.Value); + } + + // Unlock workstation if this item references one + if (itemDef.WorkstationType.HasValue) + { + state.UnlockedWorkstations.Add(itemDef.WorkstationType.Value); + } + + // Unlock adventure if this item references a theme + if (itemDef.AdventureTheme.HasValue) + { + state.UnlockedAdventures.Add(itemDef.AdventureTheme.Value); + } + + // Track cosmetic unlocks + if (itemDef.CosmeticSlot.HasValue && itemDef.CosmeticValue is not null) + { + state.UnlockedCosmetics.Add(item.DefinitionId); + } + } + + return events; + } +} diff --git a/src/OpenTheBox/Simulation/ResourceEngine.cs b/src/OpenTheBox/Simulation/ResourceEngine.cs new file mode 100644 index 0000000..41075b3 --- /dev/null +++ b/src/OpenTheBox/Simulation/ResourceEngine.cs @@ -0,0 +1,51 @@ +using OpenTheBox.Core; +using OpenTheBox.Core.Items; +using OpenTheBox.Data; +using OpenTheBox.Simulation.Events; + +namespace OpenTheBox.Simulation; + +/// +/// Processes consumable items that modify resource values, clamping results +/// within valid bounds. +/// +public class ResourceEngine +{ + /// + /// Applies the resource effect of a consumable item to the game state. + /// Returns a if a resource was modified, or an empty list otherwise. + /// + public List ProcessConsumable(ItemInstance item, GameState state, ContentRegistry registry) + { + var events = new List(); + var itemDef = registry.GetItem(item.DefinitionId); + + if (itemDef?.ResourceType is null || itemDef.ResourceAmount is null) + return events; + + var resourceType = itemDef.ResourceType.Value; + var amount = itemDef.ResourceAmount.Value; + + if (!state.Resources.TryGetValue(resourceType, out var resourceState)) + { + // Resource not yet tracked; initialize with a default max + resourceState = new ResourceState(0, 100); + state.Resources[resourceType] = resourceState; + } + + var oldValue = resourceState.Current; + var newValue = Math.Clamp(oldValue + amount, 0, resourceState.Max); + + if (newValue != oldValue) + { + state.Resources[resourceType] = resourceState with { Current = newValue }; + events.Add(new ResourceChangedEvent(resourceType, oldValue, newValue)); + } + + // Consume the item + state.RemoveItem(item.Id); + events.Add(new ItemConsumedEvent(item.Id)); + + return events; + } +} diff --git a/src/OpenTheBox/Simulation/WeightedRandom.cs b/src/OpenTheBox/Simulation/WeightedRandom.cs new file mode 100644 index 0000000..5ccb9be --- /dev/null +++ b/src/OpenTheBox/Simulation/WeightedRandom.cs @@ -0,0 +1,46 @@ +namespace OpenTheBox.Simulation; + +/// +/// Utility class for performing weighted random selections. +/// +public static class WeightedRandom +{ + /// + /// Picks a single item from a weighted list using the provided random source. + /// + /// Thrown when the entries list is empty. + public static T Pick(IReadOnlyList<(T item, float weight)> entries, Random rng) + { + if (entries.Count == 0) + throw new ArgumentException("Cannot pick from an empty list.", nameof(entries)); + + var totalWeight = 0f; + for (var i = 0; i < entries.Count; i++) + totalWeight += entries[i].weight; + + var roll = (float)(rng.NextDouble() * totalWeight); + var cumulative = 0f; + + for (var i = 0; i < entries.Count; i++) + { + cumulative += entries[i].weight; + if (roll < cumulative) + return entries[i].item; + } + + // Fallback to last entry (handles floating-point edge cases) + return entries[^1].item; + } + + /// + /// Picks multiple items from a weighted list (with replacement) using the provided random source. + /// + public static List PickMultiple(IReadOnlyList<(T item, float weight)> entries, Random rng, int count) + { + var results = new List(count); + for (var i = 0; i < count; i++) + results.Add(Pick(entries, rng)); + + return results; + } +}