JavaScript must be enabled to play.
Browser lacks capabilities required to play.
Upgrade or switch to another browser.
Loading…
<<widget QuestActive>> <<run Qum.active(_args[0])>> <</widget>> <<widget NoticeAdd>> <<set _noticeinfo = { title: _args[0], desc: _args[1] } >> <<if _args[2]>><<set _noticeinfo.goto = _args[2]>><</if>> <<run $messages.notices.push(_noticeinfo)>> <<run setup.EventRadarUI.update()>> <</widget>> <<widget "EventRadarShow">> <<run setup.EventRadarUI.show()>> <</widget>> <<widget "EventRadarHide">> <<run setup.EventRadarUI.hide()>> <</widget>> <<widget "EventTabs">> <div id="integrated-radar-box" style="width: 316px; height: 100%;"> </div> <<run setTimeout(function() { setup.EventRadarUI.embed("#integrated-radar-box", "notices"); }, 50)>> <</widget>>
<<dialogue>> A crude wooden wall stands in a corner of the temple, hung with planks scrawled with writing, rough but practical. [you]: 〖A task board? For this tiny crew? Feels like corporate crunch all over again...〗 [butler]: 【Raymond approaches the wooden wall, his eyes scanning the scrawled notes, his tone steady and resolute.】 My Lord, this is our new task board. Even with few hands, order is vital. It lists requests from us and the fleeing peasants—a first step to rebuilding. [you]: 【You nod, your eyes scanning the scribbled notes on the planks, catching lines marked with rewards.】 Good idea. I see rewards listed. By the way, we didn’t get to this earlier—how’s our food supply? [butler]: 【Raymond frowns, his tone heavy with concern, his gaze drifting toward the temple’s depths.】 Our supplies are dire, my Lord. We fled in haste, bringing only scraps of food. We need provisions urgently to shelter more fleeing peasants. [you]: 【You frown, picturing the old days of Cloudvale Pasture in your mind.】 This was a ranch before—shouldn’t there be plenty of food? [butler]: 【Raymond shakes his head, a flicker of resignation in his eyes.】 Alas, the explosion and fires destroyed everything—the pasture’s resources are gone. We must start anew. For now, we gather what little the mountains offer, but it’s no lasting solution. We need farms, a ranch, and more hands. [you]: 【You pause, your tone calm but probing.】 How many made it here with us? [butler]: 【Raymond meets your gaze, his tone grave, pointing to a rough tally board above.】 Only 80 souls, all weary. Check the tally above: population 80, food 80. With strict rations, 10 people need 1 unit of food daily—enough for 10 days. If food runs dry, starvation or desertion will strike swiftly. Take heed, my Lord. [you]: 【You take a deep breath, your gaze hardening with resolve.】 Food’s the priority. How do we boost production? [butler]: 【Raymond bows slightly, a hint of hope in his tone.】 Demons still lurk outside the temple. We must reclaim a small patch of land to recover. Captain Garvin is clearing the way—progress should come in a few days. [you]: 【You glance toward the temple’s entrance, a spark of exploration in your eyes.】 I want to check the valley outside, get a feel for the terrain. [butler]: 【Raymond’s brow furrows, his tone cautious yet firm, gesturing toward the entrance.】 My Lord, it’s unwise now. Though most demons retreated, unable to find the temple’s entrance, stragglers still lurk in the valley. Captain Garvin has sealed the valley’s mouth and built simple fortifications. Best to wait until it’s fully cleared. [you]: 【You nod, your eyes scanning the temple’s stone walls, pondering the next steps.】 〖It seems we’ll have to hold out a few days until the valley’s cleared for resource gathering and rebuilding. Hope for good news soon.〗 <<SetFlag 'butleflag' 1>> <<SetFlag 'endispatch' 1>> <<SetFlag 'tutorial' 8>> <</dialogue>>
<<dialogue>> In the temple hall, Garvin strides forward, his scarred armor glinting, eyes blazing with resolve beneath his helmet. [captain]: My Lord, good tidings! We fought fiercely and purged the valley of demon stragglers. Hold the valley’s mouth, and the land is secure! [you]: 【You nod, your gaze on Garvin approving, your tone buoyed with relief.】 Well done! We can start production and find proper homes. Packing all in the temple is no answer. Tell me how it was done. [captain]: 【Garvin stands tall, his tone resolute, carrying the grit of a fresh victory.】 The day you awoke, the demon pursuers, unable to find the temple’s entrance, mostly withdrew, leaving few lurking in the valley. I led our men to seal the valley’s mouth, built simple fortifications, and used the terrain to trap and purge the fiends. Our beastfolk allies aided the hunt—their prowess sped our work. I misjudged them; they’re strong and true allies. We also cleared the ruins, set up a resource zone and living quarters, and moved scant supplies from the wreckage to the warehouse. [you]: 【You pause, your gaze sharpening as you plan the next steps.】 The valley’s small—using it wisely is key. Once we’ve mustered enough strength, we’ll retake Hawkridge Town. [captain]: 【Garvin nods, his armor clinking softly, his tone edged with anticipation.】 We must avenge our fallen brothers! By the way, my Lord, have you noticed? The goddess aids us. In mere days, the scorched pasture has regained its green vigor. The blast crater in the valley’s heart, filled by recent rains and mountain streams, has formed a radiant round lake—a marvel divine. They say this land holds a mystic power to breed exceptional steeds, I believe it is also true. [you]: 【You nod, clapping Garvin’s armored shoulder, your tone steady, eyes sharp with resolve.】 This lake is a gift of the goddess. Let us name it Dawnveil Lake. With the goddess’s blessing and this fine land, we must first hold our ground. Garvin, the valley’s defense is yours. The griffon knights warn of a demon assault in three days. If danger strikes, we’ll all stand with you. Command every soul—man, woman, child, human, or beastfolk—as warriors. This world spares no strength for petty quarrels; only together can we survive. <<SetFlag 'enmeeting' 2>> <</dialogue>>
<<dialogue>> The chambers of the Lost Temple lie buried deep within the mountain. Moss clings to the damp stone walls, while torchlight dances across rows of iron cages and catches on the chains hanging from them. The air is thick with moisture, fear, and the occasional low growl.Some of the prisoners sit motionless in the darkness. Others cling to the bars, their fur and scales barely visible beneath the flickering light. Then, in the far corner, a flash of orange catches your eye. [you]: 〖This place is a nightmare. I’ve been in this world for barely a day, and somehow I’ve already stumbled into a dungeon full of prisoners. Yesterday, I was worrying about deadlines. Today, I’m looking at people locked in cages. And those aren’t humans. Beastfolk...? Who the hell did this?〗 [you]: 【You stop at the entrance, staring at the cages.】〖Cages? You kept living people in these things? This isn't a storeroom. It's a prison. Selina... she came with me. She must know what's going on.〗【You turn toward her.】Selina, what is this place? Who are they? [headmaid]: 【Selina steps forward, her expression tightening as she looks toward the cages.】 My Lord... they are Beastfolk. The Empire ordered their confinement about a month ago. When we fled to Hawkridge Vale, we were instructed to bring them with us. So we did. [you]: 【You look back at the cages, disbelief giving way to anger.】The Empire ordered this? You were fleeing for your lives, and you still dragged cages along with you? [you]: 【You take a breath and force yourself to calm down.】 Selina, why? What did these people do? [headmaid]: 【Selina lowers her eyes for a moment before answering.】The Empire claims the Beastfolk have ties to demons. There had always been disputes between humans and Beastfolk—trade routes, hunting grounds, territory in the forests. Nothing that ever became a war. But a month ago, demonic rifts appeared across the region. Villages were destroyed. People were killed. Around the same time, several Beastfolk tribes were found building what the Empire called "altars." The authorities concluded that the tribes were working with demons. [you]: 【You fold your arms, staring at her.】 "Altars"? That's the evidence? [you]: A month ago, demons appeared, and the Beastfolk happened to be building stone structures. So the Empire decided they must be working together? [headmaid]: 【Selina hesitates.】 Not quite. The Empire also recovered several Beastfolk relics near the rifts. Some of the markings resembled demonic runes. [headmaid]: The capital is terrified, My Lord. People wanted someone to blame. The Beastfolk were already viewed with suspicion, and the evidence—however uncertain—was enough for the Empire to act. [you]: 【You stare through the bars at the prisoners.】 So they needed a scapegoat. And these people were convenient. [you]: 【Your eyes settle on the gaunt figures inside the cages.】 Look at them, Selina. They're half-starved. If these people were really plotting with demons, they certainly don't look like they're winning. [you]: 【You step toward Selina and hold out your hand.】 Give me the keys. [headmaid]: 【Selina's fingers tighten around the keys.】 My Lord... If you release them, the people may object. And if word reaches the Empire, they may demand an explanation. [you]: 【You meet her eyes.】 Then I'll give them one. No one is staying in these cages. [headmaid]: 【Selina studies you for a moment, then quietly hands over the keys.】 As you command, My Lord. But please... be careful. [you]: 【You take the keys.】 I will. [you]: 〖The Empire can complain later. Right now, these people need food, medicine, and a way out. And if I'm going to understand what happened here, I need to hear it from them.〗 [you]: 【You walk toward the nearest row of cages.】 Who can speak for everyone here? [you]: 【A movement catches your attention.】 ...Her. @item atlas/unique/catmaid_slave.png You approach the corner cage. A young Felinefolk woman leans against the bars. Her long orange hair catches the torchlight like a living flame. Golden eyes watch you carefully, wary but far from broken. Her clothes are torn, and several claw marks run across her arms. Even so, there is still something fiercely alive in the way she carries herself. [you]: 〖She's hurt, but she's still standing. That's enough for me.〗 【You crouch beside the cage, keeping your voice gentle.】 Hey. I'm Baron Wilderen. I'm going to get you out of there. Can you tell me your name? @item clear [catmaid]: 【The Felinefolk woman studies you from head to toe. After a moment, one corner of her mouth curls upward, revealing the faintest hint of a fang.】 A baron? Huh. You don't look much like the nobles I've met. [catmaid]: I'm Stellafall. Felinefolk. [catmaid]: My mother always said I was lucky. Apparently, two hens laid double-yolk eggs the night I was born. [catmaid]: 【She flicks her tail and glances at the lock.】 So... Are you going to keep standing there, Baron? These bars are starting to get on my nerves. [you]: 【You laugh under your breath as you fit the key into the lock.】 Double-yolk eggs? I think I could use some of that luck. [you]: 【The lock clicks open.】 There. You're free. [catmaid]: 【Stellafall steps out, stretching her shoulders and rolling her wrists.】 Oh, that feels good. [you]: 【You glance at her wounds.】 Before you run off— I need to ask you something. The Empire says your people built altars and made a pact with demons. Is any of that true? [catmaid]: 【Stellafall stares at you for a heartbeat, then bursts out laughing.】 Altars? Oh, that's rich. [catmaid]: Our shamans warned us that something terrible was coming. So we fortified our villages. We stacked stone walls, dug trenches, and set traps around the rifts. [catmaid]: The Empire saw those walls and decided they were altars. Then someone found a few old symbols near the rifts, and suddenly we're demon worshippers. [catmaid]: 【She snorts, folding her arms.】 Believe me, Baron. If I ever made a deal with a demon, I'd at least ask for something useful. [catmaid]: Fish, maybe. [you]: 【You stare at her for a second before laughing.】 You're serious? [catmaid]: Very. [you]: 〖She's been imprisoned, starved, and accused of working with demons... And she's still joking about fish. I like her.〗 [you]: 【You look around the chamber.】 Stellafall. I'm rebuilding Hawkridge Vale. I could use someone like you. If you want it, come work for me. [you]: You'll have food, a roof over your head, and the freedom to come and go as you please. [catmaid]: 【Stellafall blinks, clearly caught off guard.】 Work for you? As a maid? [you]: If you're willing. [catmaid]: 【Her ears perk up.】 And the fish? [you]: Fresh. [catmaid]: 【Her tail shoots upright.】 Deal. [catmaid]: Stellafall, reporting for duty! [headmaid]: 【Selina watches the exchange with a faintly troubled expression.】 My Lord... Your kindness is admirable, but this may not be as simple as it seems. [headmaid]: The people may distrust them. And the Empire may see this as a challenge. [you]: 【You glance at the cages.】 Then we'll prove them wrong. No more cages. Not in Hawkridge Vale. [you]: 【You turn toward Stellafall.】 Take care of the others. Get them somewhere safe. From today on, they're under my protection. [catmaid]: 【Stellafall looks around at the other prisoners. Her expression softens.】 You heard him! [catmaid]: Come on. Let's get out of here. [you]: 〖For the first time since I arrived in this world, this place feels a little less hopeless.〗 You glance down. A small ceramic angel figurine rests in your palm. You don't remember picking it up. A familiar voice whispers through your mind. [leah]: 【Leah's voice reaches you like a whisper carried on the wind.】 Riftbearer... Her soul resonates with the Rift. [leah]: She can help you awaken its power. Free the others as well. Their bloodlines may become the key to surviving the Tainted Seed Epoch. [you]: 〖There it is again. Leah never explains anything properly. Still... if Stellafall can really help with the Rift, I'll need to find out how. Later. Preferably when nobody is watching.〗 [you]: 【You raise your voice across the chamber.】 Stellafall! Once you're changed, meet me in the main hall. [catmaid]: 【Stellafall grins.】 On it, Lord Baron! <<run $chars.dataMap.get("catmaid").img = "atlas/unique/catmaid_maid.png">> @item atlas/unique/catmaid_maid.png A short while later, Stellafall emerges in her new maid uniform. She turns once, examining the outfit with obvious delight. [catmaid]: Oh, this is nice! I think I'm going to like working here. [you]: Then let's get started. @item clear <<set $summon.slots to [$chars.dataMap.get("you"), $chars.dataMap.get("catmaid")]>> <<set $summon.currentsolt = 0>> <<goto 'MainHall_UI'>> <</dialogue>>
<<dialogue>> The main hall of the Lost Temple nestles deep in the mountain’s heart, moonlight leaking through a fractured dome, glinting off a charred stone dais and the hovering Planar Rift. The rift pulses like liquid stars, its low hum edged with forbidden notes, fractured runes around it whispering of the explosion that razed the ranch. The air thrums with unease, broken only by Stellafall’s sprightly steps, her orange hair flaring like flame in the gloom. [you]: 〖Back in this creepy dump... The rift’s still freaky, like it’s alive. Just got zapped here, and now I’m supposed to con Stellafall into Leah’s weird ritual? My face is burning already! She’s so chipper—how do I even start? If she slugs me, I’m screwed.〗 [you]: 【You halt before the rift, rubbing your hands, sneaking a glance at Stellafall, voice stiff.】〖Alright, deep breath... don’t choke! Leah says this ritual saves the world, but "soul resonance" sounds like... ugh, don’t go there! Test the waters, don’t spook her.〗 [you]: Uh, Stellafall, this place... kinda cool, right? 【You flash an awkward grin, pointing at the rift.】 This is, y’know, the Planar Rift—super epic thing! It can, uh, call in backup to smash demons. [catmaid]: 【Stellafall tilts her head, gold eyes gleaming, tail swinging like a pendulum, tone curious.】 Cool? Hmm... kinda spooky, like a fishbowl full of stars! 【She leans toward the rift, orange hair bouncing.】 Backup? What kinda backup? More Felinefolk like me? Or some huge beastie? 【She grins, flashing tiny fangs.】 Lord, you didn’t drag me here just for the view, did ya? Spill—what’s the fun part? [you]: 【You swallow hard, palms sweaty, forcing calm.】〖Fishbowl? Hah, her metaphors... Focus, man! How do I say "ritual"? Soul resonance? Too weird! Pitch it as an adventure—yeah, adventure!〗 [you]: Hey, the view’s just a bonus! 【You flash a grin, thumping your chest.】 This rift needs us to... light it up! Yeah, spark it! Then we’ll call in tons of badass allies to save Hawkridge Vale. You love thrills, right? This is hero stuff! [catmaid]: 【Stellafall’s eyes spark, tail shooting higher, tone buzzing.】 Hero? Ooh, that sounds awesome! 【She hops half a step, orange hair flopping.】 Light it up? How? Toss a torch? Do a dance? 【Her gold eyes narrow, half-teasing.】 Lord, you’re not making me work for free, right? Any rewards? Fish—fresh ones, heh! [you]: 【You crack a laugh, nerves easing, mind racing.】〖Fish? She’s got priorities! Fine, use fish as bait! But the ritual—say "soul-bond"? No, too lovey-dovey! Try... energy? Yeah, energy!〗 [you]: Fish? Oh, you bet! Fresh, fat ones till you’re stuffed! 【You throw a thumbs-up, voice dropping conspiratorial.】 But sparking this thing needs... energy resonance. You and me, uh, tapping the rift’s mojo! Not hard, just... a special kinda job. [catmaid]: 【Stellafall tilts her head, tail slowing, gold eyes flickering with doubt.】 Energy resonance? Sounds fancy... 【She scratches her cheek, grin wary.】 Lord, be straight—what’s the catch? I’m not diving into that starry puddle, am I? 【She points at the rift, half-joking.】 Fish are great, but I wanna know what’s up—your eyes are all sneaky! [you]: 【Your face flushes, waving hands, voice stammering.】〖Sneaky?! I’m not! Her nose is sharper than a bloodhound’s! Can’t say soul-anything—spin something solid... saving the world! Yeah, she’ll bite!〗 [you]: Catch? No catch! 【You force a chuckle, straightening up.】 Stellafall, this is the key to saving the world! The rift needs our, uh, willpower to ignite hope or whatever. Demons are wrecking stuff out there—don’t you wanna show ’em who’s boss? [catmaid]: 【Stellafall’s gold eyes narrow to slits, studying you, lips curling upward.】 Save the world? Lord, your tongue’s sweeter than a honey jar! 【She giggles, tail whipping again.】 Fine, I got willpower—Felinefolk souls don’t mess around! 【She pats her chest, orange hair bouncing.】 But deal’s a deal—after this, I want a fish feast! When do we start? I’m itching to go! Lord, why are you stripping me?! Ah~ @item event/unique_nsfw/stellafall0401235531.png [you]: 【You clear your throat, tone as solemn as you can muster.】 Stellafall, this is the Goddess’s way to save the world! Don’t be sad—I’m not hiding anything, this is the summoning ritual. Let’s see what we can call forth! @item event/unique_nsfw/stellafall0401235531.png [catmaid]: 【Stellafall blinks her gold eyes, voice with a hint of nerves.】 Oh, so that’s it? Lord, I’ve got no experience... you’ll have to guide me, ah~! @item clear <<run $chars.dataMap.get("you").setFerClear(200)>> <<run $chars.dataMap.get("catmaid").setFerClear(200)>> <<CardPop $chars.dataMap.get("you") $chars.dataMap.get("catmaid") 1>> <<run $chars.dataMap.get("you").setFerClear()>> <<run $chars.dataMap.get("catmaid").setFerClear()>> <</dialogue>>
<<dialogue>> The Rift suddenly erupts with light. Stars pour from its depths, twisting together until they form a single sphere of shimmering energy. The sphere splits. A figure steps through. A catgirl. She lands lightly on the stone dais, her tail swaying behind her. For a moment, she simply looks at you. Then she smiles. [you]: 〖It worked. It actually worked. And that's definitely a catgirl. Leah wasn't exaggerating. Still... Why do I feel like this is only the beginning?〗 [catmaid]: 【Stellafall's ears shoot upright.】 Lord... Is that— [catmaid]: 【She steps closer to the newcomer, staring in disbelief.】 Another Felinefolk. [catmaid]: Where did you come from? [Catgirl]: 【The newcomer tilts her head, studying Stellafall with the same curiosity.】I don't know. I remember the Rift. Then... I was here. [you]: 〖So that's what the ritual does. It doesn't summon a random soldier. It creates a connection through the Rift. And somehow, Stellafall was the key.〗 [catmaid]: 【Stellafall turns toward you, her expression brightening.】 You did it! I knew bringing me here was a good idea. [catmaid]: 【She suddenly sways on her feet.】 Whoa... Maybe I spoke too soon. [you]: 【You catch her by the shoulder.】 Easy. Looks like the ritual took more out of you than we thought. [catmaid]: 【She gives you a tired smile.】 I'm fine. Just... I could really use that fish now. [you]: 【You laugh.】 Deal. We'll get you something to eat and figure out what happened here. You glance down at the ceramic angel figurine in your palm. Its surface is trembling faintly. A thin thread of golden light stretches from it toward the Rift. [leah]: 【Leah's voice returns, quieter than before.】 Riftbearer. The seed has taken root. But your strength is not yet enough. [leah]: One soul answered your call. To open the Rift further... you will need to grow stronger. [you]: 〖Of course. There had to be a catch. One successful summon isn't enough. Fine. I'll get stronger. But I'm not burning Stellafall out just to prove a point.〗 [you]: 【You look toward Stellafall.】 That's enough for today. Get some rest. We've got a lot of work waiting for us back in the hall. [catmaid]: 【Stellafall smiles and nods.】 Yes, Lord. <<SetFlag 'catmaidflag' 3>> <<run $chars.dataMap.get("catmaid").calcAttrInfo("bond.vl", 1)>> <<SetFlag 'enbedroom' 2>> <</dialogue>>
<<dialogue>> In the bedroom, the energy infuser hums softly, purple arcane light glinting off Stellafall’s twitching cat ears. She stands by the bed, tail flicking excitedly, cat-like eyes sparkling with curiosity and anticipation, her maid’s gown slightly undone, revealing her flat abdomen. [catmaid]: 【Stellafall rubs her hands, voice brimming with excitement.】 My Lord! I wanna try that... belly-growing magic too! Mirael said it felt like flying, and I gotta feel that! [you]: 【You raise an eyebrow, lips curling with a hint of teasing.】 Haha, Stellafall, your curiosity’s more intense than a cat’s! Alright, ready to feel that ’floating’ sensation? [leah]: 【The ceramic angel figurine glows faintly, Leah’s voice soft yet authoritative.】 Stellafall’s constitution is suitable for the infusion ritual. Her agility and spirit will imbue the offspring with unique talents. The ritual begins—I shall guide the energy, ensuring safety. @item summon/catmaid_you/pp0526023204.webp [catmaid]: 【Purple light enveloping her petite frame, Stellafall's tail flicking faster. The infuser’s hum intensifies, her abdomen slowly swelling. She widens her cat-eyes, touching her belly, letting out a surprised meow.】 Whoa! It’s really growing! So magical! My Lord, it feels... like a warm magic orb in my tummy! [you]: 【You can’t help but laugh, tone encouraging.】 Haha, Stellafall, you’re too adorable! Feeling okay? 〖This catgirl’s enthusiasm is unstoppable...〗 [leah]: The ritual proceeds smoothly. Stellafall’s gestation is accelerated—when the time is right, send her to the Planar Rift to summon a new ally. Prepare to welcome another gifted comrade! Stellafall clutches her belly, her cat tail spinning like a propeller, face brimming with excitement and anticipation. You muse that this ritual not only boosts combat strength but also lifts the team’s morale. <<SetFlag 'catmaidFlag' 6 >> <<run $chars.dataMap.get("catmaid").calcAttrInfo("bond.vl", 1)>> <</dialogue>>
<<dialogue>> [catmaid]: 【Stellafall bursts into the hall like a gust of wind, orange braids flying behind her, cat ears twitching with excitement.】 My Lord! Did you see that? Those demon scouts couldn't even keep up with me! I had them chasing shadows all afternoon! [you]: 【You smile, folding your arms.】 I saw enough. You broke their formation, redirected their scouts, and warned us about the ambush before they could spring it. Well done. [catmaid]: 【Her golden eyes widen, clearly delighted.】 Really? You noticed all of that? [you]: Of course. A good scout isn't measured by how many enemies she defeats. She's measured by how many dangers her allies never have to face. [catmaid]: 【She freezes for a moment, then grins proudly.】 Heh... Then I'm officially a good scout. [you]: Not quite. [catmaid]: 【Her ears droop.】 Huh? [you]: You're getting promoted. [catmaid]: 【Her ears shoot straight up.】 Promoted?! [you]: Effective immediately, Stellafall is appointed as my Personal Guard Scout. Your duties are scouting, reconnaissance, battlefield support, and keeping me from making stupid decisions. [catmaid]: 【She stares at you, then points at herself.】 Me? Your personal guard? [you]: Yes. And there's one more condition. [catmaid]: 【Her tail begins wagging cautiously.】 What condition? [you]: You don't have to stay because I ordered you to. You stay because this is your home, and you choose to protect it. [catmaid]: 【Her playful expression slowly disappears. Her ears lower, and her eyes become unusually quiet.】 ...My home? [you]: As long as Hawkridge stands, you have a place here. [catmaid]: 【She suddenly throws herself against your arm, laughing through the tears gathering in her eyes.】 Then I'm staying! Nobody's getting rid of me now! I'll be the best guard scout this valley has ever seen! <<run $chars.dataMap.get("catmaid").calcAttrInfo("bond.vl", 1)>> <</dialogue>>
<<dialogue>> <<run AudioBus.playPlaylist('start_game');>> The meeting room door crashes open. Hizuki rushes inside, supporting a wounded caravan guard whose armor is torn and stained with blood. She barely gives him time to breathe before bringing him to the war table. [foxmaid]: 【Her usual composure is gone, replaced by urgency.】 My Lord! The caravan has been attacked! The Duskwood Feline Tribe is trapped with them. A demon search force has surrounded the entire area! [npc_caravanGuard]: 【Leaning heavily against the table, breathing painfully.】 Lord... they came in force. Three demon companies at least. The caravan guards and Felines are still fighting, but they're running out of ground. [catmaid]: 【The moment she hears "Feline Tribe," Stellafall goes completely still.】 ...The Felines? [npc_caravanGuard]: Yes. Duskwood. We saw their banners when the fighting started. [catmaid]: 【Her hands tighten around her bow.】 Grandpa... Everyone... [you]: How long? [npc_caravanGuard]: Maybe half a day. Less if the demons bring their heavy units forward. [catmaid]: 【She turns toward you, struggling to keep her voice steady.】 My Lord... I need to go. [you]: I know. [catmaid]: 【She clenches her fists.】 That's my tribe. I can't just sit here. [you]: And what would your duty as my Personal Guard Scout tell you to do? [catmaid]: 【She hesitates.】 ...Scout. Find their weak point. Report back. Don't throw myself into a fight. [you]: Good. You're learning. [foxmaid]: My Lord, if we wait too long, we'll lose everyone. The caravan's best guards and our most valuable trade goods are trapped there. [captain]: 【Gavin steps toward the map, already thinking through the terrain.】 A direct assault is out of the question. We don't have enough troops. [butler]: And if the demons are specifically hunting Lords, sending His Lordship forward would put the entire barony at risk. [you]: Exactly. You study the map for several seconds. Then your finger stops at a narrow mountain path leading toward the Feline settlement. [you]: We don't need to beat their army. We need to make their army beat itself. [captain]: 【Gavin looks up.】 An lure-and-ambush? [you]: A three-stage trap. Stellafall's team hits their rear. We become the target they can't resist. Then we close the valley behind them. [catmaid]: 【Her ears slowly rise.】 So I get to lead the first strike? [you]: You do. But remember: you're not going there to save your grandfather alone. [catmaid]: 【She nods firmly.】 I'm going to create an opening for everyone. [you]: Exactly. We bring everyone home. [foxmaid]: 【She bows deeply.】 Then the caravan is ready to follow your command. [you]: Gavin, assemble the stealth squad. Raymond, prepare supplies and medical teams. Hizuki, get the caravan survivors ready for extraction. [you]: Stellafall. [catmaid]: 【She straightens immediately.】 Yes, My Lord! [you]: Bring your people home. [catmaid]: 【Her golden eyes burn with determination.】 I will. <</dialogue>>
<<dialogue>> <<run setup.mapPoint_enShow("feline_duskwood")>> <<ToggleMap "barony">> (The Plan — Phase One: Blind Them) Stellafall leads the stealth squad through the western ridge. They avoid the main roads, cross the forest at night, and strike only where the demons are weakest. The objective is not destruction. It is confusion. [npc_scout]: 【A scout rushes into the command tent.】 Report! Stellafall's squad destroyed two supply caches and captured a demon signal post! [npc_scout]: Their scouts are chasing false tracks all over the western woods. The demon commanders can't tell how many troops are attacking them. [you]: Perfect. Keep them blind. [you]: Gavin, raise my banner at the eastern entrance. [captain]: 【Gavin hesitates.】 That will make us the obvious target. [you]: That's exactly what I want. (The Plan — Phase Two: Draw Them Out) The war banner rises above the valley. Shortly afterward, the demon forces begin abandoning their positions around the Feline settlement. [npc_scout]: They're moving! The main demon force is leaving the village! [captain]: They took the bait. [you]: Don't attack yet. Let them believe they're winning. Your small vanguard begins a controlled retreat toward Hawkridge Pass. [you]: One volley. Then fall back. No heroics. No chasing. Keep the formation loose enough to look frightened, but disciplined enough to remain under control. [captain]: Understood. (The Plan — Phase Three: Close the Gate) Half an hour later, the demon army pours into Hawkridge Pass. Their front ranks advance too quickly. Their center loses cohesion. Their rear continues pushing forward, unaware that the vanguard has already entered the killing ground. [npc_scout]: Lord! They're fully committed! Their entire vanguard is inside the pass! [captain]: 【Gavin looks toward the ridge.】 Archers are in position. [you]: Then close it. The hidden barricades collapse into place behind the demon vanguard. [you]: Signal Stellafall. [npc_scout]: Already done, Lord! [you]: Tell her to link up with the Felines. Protect the survivors first. The battle comes second. [captain]: And the demons? [you]: They'll discover exactly why you never chase an enemy into terrain he chose. <<goto "Battle_UI">> <</dialogue>>
<<dialogue>> <<goto 'MainHall_UI'>> Victory. The battle is over. The demons that entered Hawkridge Pass were trapped between the valley walls and your prepared defenses. Their superior numbers became a liability, and after the command structure collapsed, the remaining forces scattered into the wilderness. The cost was real, but the caravan survived. More importantly, the Duskwood Feline Tribe survived. Inside the temple hall, wounded soldiers, caravan guards, and Feline survivors gather around the warm fire. An elderly Feline with silver hair slowly approaches, leaning on an ancient wooden staff. [npc_felineChief]: 【He bows deeply.】 Noble Lord. I am the chief of the Duskwood Feline Tribe... and Stellafall's grandfather. You risked your life for people you had no obligation to save. We will never forget it. [you]: 【You help him stand.】 You don't owe me a debt. Stellafall is one of mine. Her people were in danger. That was enough. [npc_felineChief]: 【His expression darkens.】 Then I must tell you the rest. Duskwood is gone. [catmaid]: 【Her ears lower.】 Grandpa... [npc_felineChief]: The forest is poisoned. Our village is burned. We can return there, but there is no home left to return to. The hall falls silent. [catmaid]: 【Stellafall looks around at the survivors, then at you.】 My Lord... [you]: Speak. [catmaid]: 【She takes a breath.】 I used to think home was wherever the Duskwood cats were. Then I came here. I thought I was just staying until I found my tribe again. [catmaid]: But now... I don't think home has to be one place. If you'll let them stay, I'll make sure they earn their place here. [npc_felineChief]: 【He bows his head.】 That is why I came to ask you, Lord. We have hunters, scouts, gatherers, and craftsmen. We can work. We can fight. We only need a place where our people can live without fear. [you]: 【You look around the hall before answering.】 Hawkridge cannot promise an easy life. [npc_felineChief]: We don't ask for easy. [you]: The Inquisition may object. Other human lords may object. Resources are limited, and every new mouth changes the balance of this domain. [npc_felineChief]: We understand. [you]: Then we don't make this a request for charity. The hall grows quiet. [you]: We make it an alliance. [npc_felineChief]: 【His eyes widen.】 Alliance? [you]: Equal partners. You provide scouts, hunters, woodland knowledge, and military service. Hawkridge provides food, shelter, protection, and access to our workshops and trade network. [you]: Your people will not be slaves. They will not be ornaments. And they will not live here as permanent refugees. [you]: You'll be citizens and allies of Hawkridge. [npc_felineChief]: 【His grip tightens around his staff.】 And our warriors? [you]: Those who wish to serve may join my forces. Scouts will be integrated into the reconnaissance corps. Hunters can form specialized woodland units. [captain]: 【Gavin gives a small approving nod.】 Their knowledge of the forest could become one of our greatest advantages. [foxmaid]: And if the Felines join the trade network, their knowledge of herbs, hides, and forest resources could open entirely new routes. [npc_felineChief]: 【His eyes begin to shine with tears.】 Then... you truly mean it. [you]: I don't offer empty promises. If you sign the treaty, you become part of Hawkridge's future. That means responsibilities as well as rights. [npc_felineChief]: 【He places one hand against his forehead in the traditional Feline salute.】 Then the Duskwood Feline Tribe accepts. From this day forward, our claws defend Hawkridge. [you]: And Hawkridge will defend your people. [catmaid]: 【Her eyes suddenly fill with tears, but her tail is already wagging furiously.】 Grandpa... we have a home. [npc_felineChief]: 【He smiles.】 Yes, child. We do. [catmaid]: 【She turns toward you with a huge grin.】 My Lord! Then I'll work twice as hard! No—three times! I'll make sure you never regret letting us stay! [you]: 【You smile and pat her head.】 Start with eating something. Heroes fight better when they're not hungry. [catmaid]: 【Her ears shoot upright.】 Food?! [you]: Yes. Fresh fish included. [catmaid]: 【She pumps both fists into the air.】 BEST DAY EVER! [npc_felineChief]: 【The old chief laughs for the first time in many days.】 I see she has found quite a good Lord. [you]: Unfortunately, she found one who has to feed her now. [catmaid]: Hey! I heard that! The hall erupts in laughter. For the first time since the demons arrived, the survivors laugh without fear. The Duskwood Feline Tribe has lost its old home. But they have gained something else. A future. And somewhere beyond the mountains, the ruins of Duskwood still remain. Scattered Felines, wandering survivors, and those who escaped the demon raids may eventually return there. The ruined village is no longer merely a graveyard. It may become the first beacon for a people rebuilding their future. <<run $chars.dataMap.get("catmaid").calcAttrInfo("bond.vl", 1)>> <<run setup.mapPoint_enAlliance("feline_duskwood")>> <</dialogue>>
<<dialogue>> 【Holding a ledger, frowning deeply as he looks at the tree in the center of the hall】 [butler]: My Lord, I would never dare question your wisdom... but bringing a tree of this size indoors? And hanging it with... er, this glittering 'rubbish'? Is this truly in keeping with noble etiquette? This cost us a full 50 units of our wood reserves. [you]: 〖It’s called a holiday limited-edition skin, old Raymond. Besides, this tree is actually a signal booster to receive the system's holiday loot box.〗 "Raymond, this is a 'Starfall Tree.' In my homeland..., according to the goddess's instructions, this symbolizes gift—I mean, a convergence of good fortune. Trust me, the return on investment—will far exceed the cost." [catmaid]: 【Squatting at the very top of the tree, tail swishing madly, eyes locked onto a glowing glass star】 Meow! Boss! This thing... it’s glowing! Can I catch it? Can I take a bite? It looks like the egg of some high-energy prey! [headmaid]: 【Standing expressionless beneath the tree, clutching a feather duster, voice ice-cold】 Miss Starfall, if you dare break that 'ornament,' there will be no special roast meat for you tonight. That is made of extremely fragile glass, though I fail to understand why the Master would create a decoration with zero defense stats. [catmaid]: 【Fur bristles instantly; she leaps from the tree and lands gracefully】 Tch... Selina is so stingy. The Boss is better. Hey Boss, is this tree a scratching post for me? [captain]: 【Fully armored, pacing vigilantly around the tree, tapping the trunk with his sword hilt】 Baron! I understand now! This must be a new type of camouflaged defense tower! These colored spheres hanging on the branches... are they filled with alchemical explosives? So if a demon gets close—BOOM? As expected of you! Even when celebrating, you’re laying tactical traps! [you]: Gavin, while I appreciate your crisis awareness, those really are just... never mind. Just think of them as 'Spirit Totems' that buff morale. [promaid]: 【Holding a tray of steaming gingerbread—oddly shaped, some look like slimes, others like skulls】 Um... My Lord, is the story you told us before true? Tonight, will a 'Deity' in red clothes with a white beard really ride a flying deer, squeeze down the chimney, and deliver potions to us? [headmaid]: Erin, anyone attempting to invade via the chimney would be incinerated to ash by the barrier. Do not believe such illogical legends. [you]: 【Smiling, eyes deep and thoughtful】 In this world, nothing is impossible, Erin. That 'Old Man in Red' might have been a Supply Officer from the Old Era. During the winter blockades, only he could break through the blizzard to deliver hope to every trench. 〖Actually, it’s just a system airdrop patch.〗 [you]: Alright, everyone. Whether it’s demons outside, or summer heat, or winter snow... tonight is the 'Starfall Festival.' At this special node in time, the laws of the world become... slightly more merciful. 【Walks under the tree and flips a switch】 Hold out your hands. Receive the gift of this special holiday—this is your 'Settlement Reward.' [narr]: (Light cascades from the crystals on the tree, materializing into physical objects that fall into everyone's hands) [catmaid]: 【Catches a massive, pulsing ham】 Woah!! It’s meat! And... it feels like it holds some strange power! Thanks, Boss! I feel like I could sprint to the top of the snow mountain in one breath! [captain]: 【Catches a bottle of whetstone oil glowing with a cold blue light】 This... Is this the legendary 'Permafrost Oil'? It claims to cut through steel? Thank you for this reward, My Lord! For Hawkridge, I shall slay every invader! [promaid]: 【Holding a thick, yellowed book with complex alchemy symbols on the cover】 Is this... 'Basic Bio-Structure Analysis'? I don't understand the title, but the pictures inside... I feel like I can heal everyone more precisely now! Thank you, Baron! [butler]: 【Trembling as he holds a small, heavy bag of gold coins stamped with ancient runes】 This quality... this purity... Is this the legendary 'Origin Gold'? Master, this could exchange for a small fortune! Where did you get this? [headmaid]: 【Looking at her hand, holding nothing but an exquisite, glowing key】 Master? This is? [you]: It is an 'Enchanted Master Key,' Selina. With this, you won't need to carry that heavy ring of keys. You can enter and exit the temple freely with just one. [headmaid]: 【A faint blush appears on her cheeks along with a rare smile】 Thank you for your trust, my Master. Tonight’s 'Starfall Festival'... is not unpleasant. [you]: Raymond, use these gold coins to buy food and warm clothes for all the subjects in the territory. [narr]: Everyone: "Bless your benevolence!" [narr]: (The group begins to enjoy the feast, laughter echoing through the warm hall) [you]: 〖There’s no Santa Claus, of course. In this damn apocalypse, if I didn't hand out some welfare, these guys probably wouldn't survive the torment of these long days.〗 〖But...〗 【Glances back at Gavin fighting Starfall for a bone, and Selina wiping crumbs from Erin’s face】 [you]: 〖If, at the end of a broken world, I can preserve a small, warm buffer zone like this...〗 〖Maybe that’s the meaning of my existence as a 'Savior'.〗 [you]: 【Raises glass】Merry Christmas. To this buggy, broken world. Received: Gold x 1000, Food x 100, Wood x 50 (Tree cost reimbursed). <<pickup $storage "wood" 50 "food" 100 "gold" 1000>> <</dialogue>>
<<dialogue 'MainHall_UI'>> Consciousness surges back like a rising tide, and the hard stone slab beneath you digs painfully into your back. Firelight flickers across the rough-hewn rock walls, filling the air with the smell of scorched earth and ancient dust. This is a temple buried deep in the mountains of Hawkridge Vale—fortress-solid, silent, and steeped in mystery. Mid-May sunlight filters through cracks high above, illuminating a shattered statue of a goddess. Its worn face is still regal, watching over the stone slab below. Four figures stand nearby, their hushed voices tense, their gazes fixed on you in very different ways. You force your eyes open. Your throat is parched, and panic prickles through you as you slowly push yourself upright. Looking down, you're stunned to find that your suit is gone. In its place is heavy armor, crusted with dried blood, with fresh-looking scars marking your chest and arms. [you]: 【You mutter, touching your forehead. Your fingers come away sticky with blood, and the unfamiliar weight of the armor makes you sway slightly.】My head is killing me... Where am I? Who... are you people? [headmaid]: 【A woman steps forward quietly, a damp cloth in her hands. Her expression is gentle, though a trace of tension lingers beneath it. She carefully dabs your forehead, her touch soft but hesitant.】My Lord, you're awake... Thank the heavens. I'm Selina, your head maid. This is a hidden temple deep within the mountains of Hawkridge Vale. We fled here after demons came hunting us. [captain]: 【A broad-shouldered man in battered, scarred armor steps forward. Anger and guilt roughen his voice. His fists clench as he lowers his head.】Baron, I'm Garvin, captain of your guard. Yesterday morning, a tremendous explosion echoed from the mountains north of town, throwing the townsfolk into panic and fueling all manner of rumors. By noon, a demon legion had breached the western wall in the chaos and stormed the keep. I fought to get you and the townsfolk out... but I failed to hold Hawkridge Town. [you]: 【You frown, feigning confusion as the armor's weight settles heavily on your body.】〖Baron? You're kidding me. I'm just an office worker. This armor, these wounds... I was working late when the lightning hit. Demons came after me? Why would they be hunting me in the first place? And this temple, that explosion... they have to be connected. Keep your head down. Find out what's going on before you make a mistake.〗 [you]: 【You hesitate, rubbing your temples as though trying to piece together the fragments of your memory.】I see... Go on. Tell me everything. [butler]: 【An older man with graying hair steps forward. He wears an emerald-green coat, and his measured tone carries a quiet gravity. His sharp eyes study you carefully as he bows.】My Lord, I'm Raymond, your steward. I've served the Wilderen family for thirty years. We fled to Hawkridge Vale for safety, where your family's ancestral estate—once known as Cloudvale Pasture—stood. The estate was renowned for breeding fine warhorses. But when we arrived, we found it completely destroyed by an unknown explosion. I believe that was the source of yesterday's blast. Beneath the ruins, we discovered an entrance to this temple hidden within the estate grounds. With nowhere else to go, we took refuge here. [headmaid]: 【Selina continues, her voice gentle but uncertain. She glances toward the shattered statue, a flicker of awe in her eyes.】My Lord, the entrance to this temple seems to have been concealed by some ancient power, one that may be tied to your family's legacy. Perhaps it was the goddess who protected it. The demons chased us all the way to the edge of the vale, yet once we entered these ruins, they somehow lost our trail. While you were unconscious, we prayed to the goddess for your recovery... and that was when a faint light descended from the statue and surrounded you. [promaid]: 【A young woman huddles behind Selina, head bowed. She bunches her skirt tightly in her fists, her voice almost lost beneath the crackle of the firepit.】M-My Lord... I... I'm Erin, an apprentice maid. You're all right... Thank goodness... [butler]: 【Raymond looks at you, his expression grave.】The Wilderen family's warhorse breeding was unmatched, but the old Baron kept its methods a closely guarded secret. This temple... it may have something to do with the family's past, but we know nothing for certain. And with demons ravaging the empire and our supplies running low, this place is our last refuge. [captain]: 【Garvin grits his teeth, his voice low and thick with hatred. He pauses, his eyes burning with resolve.】Those demons brought beasts with them—furred, clawed things more savage than any beastfolk I've ever seen. I watched them tear my men apart... My Lord, I failed Hawkridge Town, but I swear this temple will not fall! [headmaid]: 【Selina interrupts gently, her voice calm but firm. She looks toward Garvin, her expression composed.】Garvin, steady yourself. Those were not beastfolk. They were monstrosities twisted by demonic corruption. If we misjudge what we're facing, we put everyone here in greater danger. [promaid]: 【Erin lowers her head, still clutching her skirt. Her voice is nearly drowned out by the firepit. She steals a quick glance at you, something unreadable flickering in her eyes, then turns away at once.】I... I'll go check on the wounded. [you]: 【You take a slow breath, forcing yourself to stay composed. Your eyes move between the shattered goddess statue and the four people around you, the weight of the armor still feeling utterly foreign.】〖Demons came after a "baron"? I'm just an office worker. This armor, these wounds, the destruction of the estate, this temple... none of it makes sense. And now there's the light from that statue, and Erin's strange reaction. There are too many pieces that don't fit. I'll have to uncover the truth myself—and whatever happens, I can't let them realize I don't know who this Baron is supposed to be.〗 [you]: 【You draw a slow breath, then rise to your feet and brush the dust from your armor. You keep your expression steady, forcing yourself to sound like the lord they expect you to be.】All right. I understand. First, take stock of our supplies, check on the wounded, and strengthen our defenses. Then show me the temple. I need to know how much ground we have, what we can use, and how long we can hold this place. [you]: 〖I don't know who this Baron was. But until I find out, I have to become him.〗 <</dialogue>>
<<dialogue>> The first days pass beneath a gray and uncertain sky.Hawkridge Vale is far from safe, but the immediate chaos has begun to recede.The wounded are recovering.The surviving guards have restored the most damaged defenses.Food has been counted, weapons sorted, and every scrap of useful material recorded.For the first time since your awakening, the people around you are no longer simply waiting for the next disaster. They are waiting for orders.At your suggestion, one of the larger chambers is cleared and furnished with a long wooden table.Maps cover one wall.Reports, supply lists, patrol schedules, and messages from the surrounding settlements cover another.It is not much.But it is a beginning. [you]: 【You look around the newly arranged chamber.】From now on, we make decisions here. [butler]: 【Raymond studies the room thoughtfully.】A proper council chamber. [you]: Call it whatever you like. [you]: 【You place a hand on the map of Hawkridge Vale.】I need information before I make decisions.Every report comes here. Every patrol reports here. If something unusual happens, I want to know before rumors reach the streets. [captain]: 【Garvin nods.】Understood. [headmaid]: 【Selina gives a faint smile.】Then this room will become the heart of the stronghold. You look at the maps again.The valley.The ruined estate.The hidden temple.And beyond them, the territories already swallowed by the Dark Legion. [you]: 〖I'm still trying to understand why I'm here. But that can wait. If this place is really mine now... then I need to keep it alive.〗 <<run Msg.notify("Meeting Room Unlocked.");>> <<SetFlag 'endintro' false>> <</dialogue>>
<<dialogue>> [headmaid]: 【Selina approaches softly, her tone gentle.】Good morning, my Lord. The wounded from our escape have nearly recovered, and we should be able to remove their bandages today and tend to their wounds properly. Do you approve? [you]: 【You stretch, rubbing your eyes.】〖Finally, some good news. Maybe we can breathe a little easier.〗Morning, Selina. Sounds good. Go ahead, and make sure everything's cleaned and treated properly. If the wounded are recovering, I'm sure it'll lift everyone's spirits. [headmaid]: 【Selina nods, smiling faintly.】Thank you, my Lord. I'll see that it's done properly. [you]: 【You raise an eyebrow, probing casually.】〖Erin's been keeping a low profile lately. Hope she's not getting herself into trouble.〗Oh, by the way, Selina... I hardly ever see that apprentice maid anymore. Erin, wasn't it? Where's she been? [headmaid]: 【Selina pauses, a hint of hesitation crossing her face.】Erin... she's been rather busy, my Lord. She's been helping tend to the wounded, and there's been quite a lot for her to handle. And since she's still in training, she has duties to attend to as well. So... you simply haven't had much chance to see her. [you]: 【You scratch your head, keeping your tone light while watching her closely.】〖Busy enough to stay out of sight this long? Something about that answer feels off.〗Oh? That busy, huh? Sounds like she's got her hands full. Alright, tell her not to push herself too hard. She should come by the hall once in a while. Everyone here cares about her. [headmaid]: 【Selina chuckles softly, masking her unease.】Yes, my Lord. I'll pass that along. I'm sure Erin will be grateful for your concern. After several days of rest, the human survivors have recovered remarkably well. Their wounds have healed, their armor has been repaired, and they are ready to face greater challenges. (All character portraits updated.) Selina's report brings a sense of calm to the bedroom, but her hesitation whenever Erin comes up leaves one thing clear: she's hiding something. [you]: 〖What is Erin really up to? Something's definitely off. I'll have to look into it later.〗 <<run $chars.dataMap.get("you").img = "atlas/unique/you_armor.png">> <<run $chars.dataMap.get("you").atlas.unique.cloth = "you_armor">> <<run $chars.dataMap.get("promaid").img = "characters/promaid/promaid02.webp">> <<run $chars.dataMap.get("captain").img = "characters/captain/captain02.webp">> <<run $chars.dataMap.get('butler').img = "characters/butler/butler02.png">> <<run $chars.dataMap.get('headmaid').img = "characters/headmaid/headmaid02.webp">> <</dialogue>>
<<dialogue>> Garvin strides forward, tone wary. [captain]: My Lord, someone seeks an audience. A foxfolk woman has arrived with several guards and caravan merchants. She doesn't appear hostile, but those silver tails... I've never seen a foxfolk like her before. She claims to be the chief scout of the Silvertail Clan and insists on meeting you. [you]: 【You frown thoughtfully, then nod.】 〖Foxfolk... So the black market's hidden hand is finally stepping into the light.〗 Let her in, Garvin. Stay alert. We need allies, but desperation is no excuse to lower our guard. Hizuki Silvertail enters the main hall with effortless confidence, her two silver tails swaying behind her. Her fox ears twitch as her sharp amber eyes sweep across the room. [foxmaid]: Hmph. So you're the Lord of Hawkridge Vale. This mistress is Hizuki Silvertail, chief scout of the Silvertail Clan. I didn't travel all this way for idle conversation. Your valley is well hidden, naturally defensible, and—more importantly—it has survived several demon attacks. Not bad, Lord. You may actually be worth doing business with. [you]: 【You study her carefully, keeping your voice calm.】 The Silvertail Clan. Your nomadic caravans were already known before the war. I'm Baron Wilderen. So tell me, Miss Hizuki—why come here now? [foxmaid]: 【Hizuki gives a sly smile, one tail flicking behind her.】 Because I'm tired of watching good merchants die on bad roads. You've heard of the black market, haven't you? Every Thursday, it opens somewhere along the old trade routes—materials, alchemical supplies, medicines, rumors, intelligence... People call it a mysterious underground network. They're not entirely wrong. The Silver Tail Caravan has been keeping that network alive. [you]: 【You narrow your eyes slightly.】 A trading network operating in territory controlled by demons. That's not just dangerous. That's practically suicidal. Why would the Silvertails take that risk? [foxmaid]: 【Hizuki waves one hand dismissively, though her expression turns more serious.】 Because someone has to. Foxfolk have our illusions. We can read the wind, smell corrupted ether, and slip past patrols that would slaughter a human caravan. Our caravans don't just move goods, Lord. We move information. And right now, information may be worth more than gold. [foxmaid]: 【She leans forward slightly, her smile returning.】 So here's my proposal. The Silver Tail Caravan establishes a permanent camp in Hawkridge. Every Thursday, we'll bring supplies, medicine, trade goods—and whatever intelligence we've gathered. You provide safe passage and protection within your territory. Simple, fair, profitable. [you]: 【You remain silent for a moment.】 〖A permanent trade route... reliable supplies... intelligence from beyond our borders. This could change everything.〗 The offer is attractive. But why Hawkridge? There are safer settlements farther from the front. [foxmaid]: 【Hizuki's eyes narrow with amusement.】 Because I wanted to see you. Don't look so surprised. A lord who survives a demon assault, and still has enough nerve to build a future instead of hiding behind his walls... That's rare. This fox wanted to know whether the rumors were true. [captain]: 【Garvin grips his sword hilt, clearly irritated.】 Foxfolk, watch your tone. My Lord is offering protection, not begging for your business. [foxmaid]: 【Hizuki snorts, but her ears twitch with slight unease.】 Confidence, knight. Not arrogance. Your lord needs supplies. I need a secure base. Neither side gets everything they want. That's what makes it a partnership. [foxmaid]: And if this first deal succeeds, we can talk about something bigger. Alchemy. Workshops. A proper trade network. Maybe even a permanent free-trade zone. [you]: 【You exchange a brief glance with Garvin before nodding.】 〖She's troublesome. But she's also exactly the kind of trouble a recovering territory needs.〗 Garvin, clear out the old black-market site at the valley entrance. Let the Silver Tail Caravan establish its camp there. Miss Hizuki—welcome to Hawkridge. <<SetFlag 'foxmaidflag' 1>> <</dialogue>>
<<dialogue>> [captain]: My Lord, Hizuki is here. [foxmaid]: 【Hizuki strolls in with a stack of documents under one arm, her two silver tails swaying proudly.】 Haha~ Still working this late, My Lord? Don't tell me you've finally discovered that running a territory is harder than fighting demons. [you]: 【You glance at the documents in her arms.】 I want to know what you meant by "something bigger" last time. If you're here to make another proposal, get to the point, Hizuki. [foxmaid]: 【Hizuki drops the documents onto the table with a satisfied grin.】 Simple. We turn the old black-market site at the valley entrance into a proper "Exotic Free Trade Zone". [foxmaid]: Human taverns. Slime alchemy stalls. Merfolk pearl merchants. Arachne silk workshops. Feline hunters. Foxfolk caravans. Invite everyone. The Thursday black market is pocket change compared to what this place could become. [foxmaid]: 【She points dramatically toward the map.】 Seven days a week. Permanent merchants. Tax revenue. Foreign goods. Information from every corner of the region. Give me the land, and I'll turn this valley into the safest marketplace between the mountains. [captain]: 【Garvin immediately steps forward, frowning.】 My Lord, allowing that many foreign races into the valley creates obvious security risks. If infiltrators enter among the merchants, we could be inviting the enemy through our own gates. [foxmaid]: 【Hizuki folds her arms, looking unimpressed.】 And that's why you have me. Foxfolk can detect corrupted ether, demon traces, forged identities, and suspicious cargo. I'll establish inspection rules for every caravan. No registration, no entry. [foxmaid]: Silver Tail Caravan handles security coordination, merchant registration, and tax collection. Hawkridge keeps military authority and final jurisdiction. You don't surrender control, My Lord. You gain an economic engine. [mirael]: 【Mirael folds her arms, giving Hizuki a suspicious look.】 Little fox, you're awfully confident for someone asking my Lord to hand over half a district. [foxmaid]: Confident? Of course. I already calculated the projected revenue. [headmaid]: 【The head maid quietly reviews the figures.】 My Lord... She's not exaggerating. Even under conservative estimates, the trade zone could eliminate our current operating deficit within a few months. More importantly, it would allow us to support a much larger population and military. [you]: 【You stare at the financial projections, then sigh.】 〖I came here to fight demons. Somehow I'm becoming a trade minister.〗 Fine. We'll use the old black-market grounds. But the trade zone operates under Hawkridge law. [foxmaid]: 【Hizuki's ears perk up immediately.】 Excellent! And I've already picked the name! Silvertail Grand Exchange! Glowing magic-crystal signs, elegant caravan towers, and perhaps a magnificent statue of this beautiful fox at the entrance— [you]: No statue.And the name is changing. [foxmaid]: Eh?! [you]: 【You point firmly at the map.】Hawkridge Free Trade Zone. Bring me the complete contract tomorrow morning. Revenue split. Security regulations. Merchant registration. Non-human residency rules. Taxation. Military jurisdiction. Every clause. I'll read every word. [foxmaid]: 【Hizuki smiles, eyes narrowing playfully.】 Kyaa~ So strict, My Lord. You really do make negotiations interesting. [captain]: 【Garvin half-draws his sword with a sharp metallic sound.】 That's enough, fox. Show some respect to His Lordship. [headmaid]: 【The head maid quietly gathers the documents.】 I'll have Erin prepare some tea. My Lord, please get some rest before the next mountain of paperwork arrives. [you]: 【You lean back in your chair, staring at the ceiling.】 〖I just wanted to rebuild a home... How did I end up becoming the umbrella for half the region's merchants?〗 <<SetFlag 'entrade' 2>> <<run $chars.dataMap.get("foxmaid").calcAttrInfo("bond.vl", 1)>> <</dialogue>>
<<dialogue>> <<goto 'Bedroom_UI'>> Later that night, after the celebrations in the temple hall finally die down, the quiet rustle of silk breaks the silence of your study. Hizuki steps inside carrying a bottle of fine spiced wine. Her two silver tails sway slowly behind her in the candlelight. [foxmaid]: 【Hizuki gives a small bow. For once, there is no teasing grin on her face—only quiet sincerity.】 Still awake, My Lord? I brought something to help you unwind after today. [you]: 【You look up from the paperwork with a faint smile.】 Hizuki. I didn't expect you to visit this late. How is the caravan settling in? [foxmaid]: 【Hizuki places the bottle on the desk and steps closer.】 They're safe. Everyone has food, shelter, and a place to sleep. Because of you. When I arrived here, I thought Hawkridge was just another potential trade partner. Then I watched what you did. You risked your own life to save my people... and the Feline tribe. [you]: 【You lean back slightly, meeting her gaze.】 It was my responsibility as the Lord of Hawkridge. I couldn't simply watch people under my protection die. [foxmaid]: 【Hizuki takes a slow breath. Her expression grows unusually serious.】 That's exactly what I mean. I've met plenty of lords. Some talk about duty. Some talk about profit. Some talk about survival. But most of them abandon outsiders the moment keeping them becomes inconvenient. [foxmaid]: 【She places one hand over her chest.】 You didn't. You looked at us and decided we were worth protecting. So I've been thinking. [foxmaid]: I don't want to be just a merchant dealing with Hawkridge anymore. I don't want the Silvertail Caravan to remain something that stands outside your walls. I want to help build this place. [foxmaid]: 【Hizuki kneels beside your desk, her expression calm and resolute.】 Therefore, I'm making a new proposal. Not from the Silvertail Caravan. Not from the Silvertail Clan. From me. I, Hizuki Silvertail, pledge myself to Hawkridge Vale. [foxmaid]: I will manage the trade zone, organize merchant routes, oversee supplies, gather intelligence, and help build the economic network this territory needs. Wherever my skills are useful, I will serve. [you]: 【You study her for a long moment before offering your hand.】 So... The fox who once came here to test whether I was worth doing business with is now asking to become part of my administration. [foxmaid]: 【Hizuki's ears twitch, and her familiar mischievous smile finally returns.】 Don't misunderstand, My Lord. I'm still evaluating the investment. I've simply decided that you're worth investing in. [you]: 【You smile and firmly shake her hand.】 Then welcome to Hawkridge, Hizuki. From today onward, you aren't merely our trading partner. You're one of us. [foxmaid]: 【Hizuki's eyes soften for a moment before she hides it behind a playful smile.】 A lifetime partnership, then, My Lord. And remember— I'm a merchant. I always collect what I'm owed. [foxmaid]: 【Hizuki's smile turns teasing.】 And perhaps, My Lord... some debts are more interesting to collect personally. <<if !$chars.templeList.includes("foxmaid")>><<AddMonster $chars.dataMap.get("foxmaid")>><</if>> <<run $chars.dataMap.get("foxmaid").calcAttrInfo("bond.vl", 1)>> <</dialogue>>
<<dialogue>> <<set _chiefTalk to random(1, 5)>> <<if _chiefTalk === 1>>[npc_felineChief]: My Lord, welcome back to our ancestral grounds. Knowing our people are safe beneath your protection in Hawkridge brings peace to this old heart.<</if>> <<if _chiefTalk === 2>>[npc_felineChief]: Duskwood was once a rich and beautiful land, My Lord... but the corruption has made these woods treacherous. Keep your guards alert whenever you travel beneath these dark boughs.<</if>> <<if _chiefTalk === 3>>[npc_felineChief]: Keeping someone here was the right decision, Lord. We have already found traces of wandering Feline kin nearby. This outpost may yet become a beacon for those who have lost their way.<</if>> <<if _chiefTalk === 4>>[npc_felineChief]: More wandering Felines will hear of our alliance in time. When they arrive, I shall send the able-bodied hunters and scouts to your barony. We must rebuild our people together.<</if>> <<if _chiefTalk === 5>>[npc_felineChief]: I hope my granddaughter, Stellafall, isn't causing you too much trouble. She has always been reckless... but when someone she loves is in danger, that girl would charge straight into a dragon's jaws.<</if>> <</dialogue>>
<<dialogue>> The main hall of the Lost Temple, carved deep in the Hawkridge Mountains. Sunshine seeps through cracks in a gothic dome, glinting off rubble and charred scars, intricate stone spires whispering of lost grandeur. The air hums with burnt wood and the rift’s eerie pulse, where a shattered platform holds a Planar Rift, liquid starlight pulsing with menace. Flickering rune fragments circle it, hinting at the cataclysm that razed the Wilderen family’s ancestral estate. [you]: 〖This place feels like a gothic ruin from some blockbuster... Last night I was slaving over slides, now I’m stuck in this nightmare with a murky baron title. The rift’s like a black hole, buzzing till my scalp crawls. The family estate blasted to rubble—bet it’s tied to this thing.〗 You tread carefully over rubble, armor chafing your shoulders, eyeing the rift with curiosity and caution. [you]: 〖This rift’s no joke... like it’s alive, breathing. Nobody’s here—should I call out? Better not summon some beast.〗 Oi! What’s this thing? Who left some arcane trinket lying around? A soft clink sounds—you spot a ceramic angel figurine rolling from the debris, palm-sized, glowing faintly gold. Before you can blink, it floats upward, hovering by the rift. [you]: 〖Holy hell! This thing moved on its own! Not just some relic, huh? That gold glow... same eerie light as that cursed blood moon last night. Heart, slow down—stay cool!〗 The figurine stays still, but an ethereal voice blooms in your mind, soft as water, edged with forbidden echoes. [leah]: This is a Planar Rift, forged from a demon’s portal, a crossroads to countless realms. [leah]: 【Tone calm, probing.】 I am Leah, Goddess of Fertility... your soul, tempered by mortal trials, stirred my fading spark, wanderer from afar. [you]: 【You stumble back, pointing at the figurine, voice rising with shock.】 〖Talking in my head! Same trick as that blood moon last night... and this baron nonsense. A ceramic doll chats? Fine, I’ll bite—gotta grill her, no getting conned.〗 [you]: You... you’re this busted angel trinket?!【Swallowing hard, you fake calm.】 Wanderer from afar? So you yanked me here, slapped with this weird baron title? Spill it—why pick a nobody like me? What’s this rift deal? [leah]: 【The figurine quivers, gold light pulsing, voice heavy with exhaustion.】This trinket is but a vessel for my dwindling spark... I cannot appear, only whisper through souls. [leah]: 【Tone deepens.】 I wrested the rift from demon hands to save the Tainted Seed Epoch, but its unstable power shattered the estate and drained my divinity. Your steadfast soul is fit to wield it. [you]: 【Eyes wide, you gesture at the temple’s ruins, tone dripping with doubt.】 〖She blew up the family estate?! This goddess is a walking disaster! Tainted Seed Epoch? What’s that nonsense? Steadfast soul? What’s so special about a desk jockey like me? Make her come clean.〗 [you]: Hold up—you turned the estate to rubble?! 【Taking a deep breath, you curb your anger.】 That creepy blood moon last night—your work, right? Why drag me here? What kind of goddess are you? Why’s the world on me? What’re the demons chasing? No cryptic games! [leah]: 【The figurine’s gold dims, voice a weary sigh.】 I am Leah, Goddess of Fertility and Life, guardian of the cycle... my power now fades. [leah]: 【Pausing, evasive.】 Demons crave the rift to burn this world to cinders. Divine laws bind me from direct defiance. Only you, wielding the rift’s might, fueled by the temple’s ancient runes, can halt this doom. [you]: 【Arms crossed, you squint at the figurine, tone skeptical.】 〖Fertility goddess? That’s a wild title. Divine red tape screwing with this? She’s hiding something—glossing over the demons like I’d bail. The rift’s got potential—let’s figure out how it works.〗 [you]: Alright, world’s collapsing, got it. But how’s this rift work? Summoning help? You’re not tossing me into a demon fight, are you? Who do I grab—any randoms work? Also, the estate’s gone—is this thing safe? No more surprise explosions, right? [leah]: 【The figurine spins slowly, voice swelling with majesty, tinged with urgency.】 The rift is a gate of life, activated by a soul-binding ritual, drawing power from the temple’s ancient runes to awaken hope. [leah]: 【Tone softens, guiding.】 Find two bound by trust or heart for the ritual, to birth a Life Seed. If successful, the rift opens, calling allies—from this realm’s monstergirls to otherworldly astral wardens. But tread carefully—it drains Energy, and failure only delays your summons, its success tied to their Fertility. [you]: 【Your jaw drops, coughing hard, face flushing red.】 〖Soul-binding?! Not planting trees—it’s... matchmaking?! This quest’s crazier than any dungeon crawl! How’s a desk jockey stuck with this? Gotta double-check, no falling for traps.〗 [you]: What?! Soul-binding ritual?!【Scratching your head awkwardly, voice stumbling.】 You mean... playing matchmaker, maybe even jumping in?! Charisma, Willpower—what, like a stat check? You fertility goddess, you’re way out there! How’s this ritual go down? [leah]: 【The figurine’s gold flashes, voice laced with amusement.】 Life springs from the union of souls—mortals need not shy away. [leah]: 【Tone turns solemn.】 The ritual needs two souls aligned in heart—or you may join them—Charisma to spark connection, Willpower to hold fast, Fertility to nurture hope, powered by the temple’s runes. Stamina and Dexterity shape the Energy’s cost. [leah]: The stronger their bond, the mightier the Life Seed, the greater the allies summoned. [you]: 【Rubbing your face, tone half-resigned, half-snarky.】 〖Alright, grow a spine, deal with this madness. Rune-powered ritual sounds legit—like a party raiding a dungeon, gotta pick the right crew. Energy, stats? Pure bard’s tale stuff. Gotta nail this down or I’m toast.〗 [you]: Okay, soul-binding, picking a crew for the ritual—kinda getting it. 【Grimacing, you shrug.】 But there’s a catch, right? Where’s this rune Energy from? What if it runs out? And if it flops—won’t curse me or anything, will it? [leah]: 【The figurine rises slightly, voice soft yet cautionary.】 The ritual flows like a river, driven by Energy from the temple’s runes, gathered over time. [leah]: 【Tone eases, soothing.】 Failure harms none, only delaying your summons, but drained Energy demands rest to restore. Success births a Life Seed, unlocking mightier allies. [leah]: Choose companions wisely—their Strength and Charisma shape the outcome. Dexterity lowers Energy costs, but plan carefully. [you]: 【Staring at the rift, brow furrowed, tone sarcastic.】 〖Like rolling dice for epic loot—pick top-tier allies, hope for luck... That blood moon last night was creepy as hell, bad vibes. Failure’s fine, success gets backup, not a bad deal. She’s cagey about limits—definite trap. What’s in the summon pool?〗 [you]: Got it—like rolling dice, gotta get the right combo.【Grinning to hide nerves.】 These summons trustworthy? What do we get? Monstergirls sound like tavern gossip—or some real heavyweights? Don’t stick me with a bunch of enemies! [leah]: 【The figurine’s gold trembles, voice low and cryptic.】 The rift is an abyss, its summons unpredictable. [leah]: 【Pausing, tone grave.】 A stronger Life Seed calls mightier allies—from forest Felinefolk to voidborn Starbeasts. Yet, a flawed ritual or a Blood Moon’s dark omens may stir the rift, summoning hostile forces. Beware the Blood Moon most of all—when it rises, the rift may twist, unleashing unknown perils. [you]: 【You pause, tapping the figurine, voice steadying.】 〖Blood Moon? That creepy thing yanked me here. High risk, high reward... She’s hiding stuff, and that estate explosion’s not the whole story. Fine, I’ll play along—I’ll dig up her game.〗 [you]: Man, Blood Moon’s got a cursed vibe...【Chuckling wryly, you glance at the rift.】 Alright, Leah, I’ll try this ritual. Gotta round up some folks—hope your rift doesn’t blow again and this crumbling temple holds up. [leah]: 【The figurine settles into your hand, its gold fading, voice dissolving like a breeze.】 Go, guide of the rift... the Life Seed will spark hope for the Tainted Seed Epoch. The rift hums once, runes dimming, silence falling, leaving only the cold, heavy ceramic angel in your grip. [you]: 〖Another goddess with secrets... Time to rally allies!〗 <<run Msg.notify("Unlock [<span>Planar Rift</span>] Zone")>> <<run Msg.notify("Unlock [<span>Summoning Ritual</span>] Function")>> <<run Msg.notify("Unlock [<span>Chambers</span>] Zone")>> <</dialogue>>
<<dialogue>> [you]: 【You grip the ceramic angel figurine, voice probing.】 Leah, can the creatures from the Dark Legion perform the summoning ritual? Won’t it attract enemies? [leah]: 【The figurine’s light flickers, Leah’s voice ethereal with a hint of amusement.】 You’ve misunderstood the summoning ritual. It’s faction-neutral. Those summoned will always be your allies, regardless of whether the summoners are from an enemy faction. [you]: 【You raise an eyebrow, tone laced with surprise and probing.】 That powerful? So, the summoned beings automatically join my side? Because I’m thinking of trying it with a succubus. [leah]: 【Leah’s voice softens, laced with mystery.】 No need to worry. Succubi are highly suitable for the summoning ritual. They have a high Fertility Talent, and their bodies can withstand the infusion of magical energy. I think it’s time to activate the gestation function. [leah]: 【The figurine’s gold light swirls, Leah’s tone deepening.】 Instead of summoning through newly formed life seeds, you can use direct gestation. By infusing magic and energy, you can accelerate pregnancy. Once the child is born, send it into the Planar Rift for 18 years of nurturing, then recall it. Due to the unique nature of time and space, this process is instantaneous. [you]: 【You frown, tone probing yet puzzled.】 So, the summoning we talked about before is actually nurturing a life seed for 18 years and then recalling it, right? [leah]: 【The figurine falls silent briefly, Leah’s voice tinged with amusement.】 ... You’re remarkably perceptive. To avoid drawing the attention of a certain special entity, I erased their memories. Do you need me to erase yours as well? [you]: 【You let out a nervous laugh, heart racing, waving it off.】 Haha, no need to worry about my wild guesses! 〖Guess I’ll stop probing here.〗 [leah]: 【Leah’s voice steadies, carrying a touch of authority.】 In any case, succubi are perfectly capable of participating in the summoning ritual. I’ve also activated the gestation function. If you build an energy infuser in your bedroom, you can unlock gestation abilities there. To hasten my divine power’s recovery, I’ve suppressed the gestation abilities of all creatures in the temple. This will also enable the accelerated pregnancy infusion function. [leah]: 【The figurine’s light dims, Leah’s tone solemn.】 The benefit of this approach is that it reduces randomness, making it easier to nurture offspring that combine the strongest attributes of their parents, potentially even surpassing their innate talents. However, this process greatly depletes the divine power I’ve painstakingly amassed, so I can only grant it to a select few. [you]: 【You nod, eyes glinting with excitement yet caution.】 Got it! I’ll give it a try as soon as possible! <<SetFlag 'leahflag' 3 >> <<SetFlag 'miraelflag' 3 >> <<goto 'Summon_UI'>> <</dialogue>>
<<dialogue>> Deep within the Lost Temple, flickering firelight casts shadows on the ceramic angel statue of Leah, the Goddess of Fertility. [you]: Leah, I get that the allies from the portal are getting stronger. But what about the monster girls I already have? I can’t just rely on newbies! Is there a way to boost their strength? Especially their potential tiers—I want to forge some real powerhouses. Being a savior’s no joke! [leah]: All things follow the natural cycle: birth, growth, decay. Regular training can hone skills and boost attributes, but potential is innate, like the paths of the stars—near impossible to change. [you]: Innate potential? That’s brutal! If their attributes are capped by potential, some monster girls are great at magic, others just swing fists. How am I supposed to build a balanced team? Saving the world needs something extraordinary! You’re a goddess—got any special tricks up your sleeve, or is it just grind and sweat? [leah]: Mortals and their impatience are always amusing. Indeed, that’s the usual way. But at the edge of life and death, a being can unleash power beyond the ordinary, shattering its fated limits. My strength has recently recovered a bit. I can infuse my divine power into a being at its most intense state, breaking the chains of potential. In your world’s terms, it’s like “unlocking the genetic code.” [you]: Sounds intense! How do we pull it off? You’re not gonna tell me to throw them into a demon horde to fight for their lives, are you? [leah]: I govern fertility. The moment of creation is life’s purest, most exalted peak. In that state, I can channel my power to ignite their potential and shatter innate limits. But it requires the life’s essence to bloom fully, with soul and body at their zenith. [you]: Wait… you’re saying it’s that kind of thing? The whole… procreation deal? [leah]: Mortal, the beauty of life lies in creation and continuity. Only in that ultimate union can life touch its core.My energy is limited, so it must be reserved for the chosen few. Go to the bedchamber and check the new ability I’ve unlocked for you. There, you’ll find your answers. [you]: Alright… I’ll check it out! [leah]: Go, Chosen One. The blooming of life’s essence will ignite your hope—and bring other surprising rewards. <<SetFlag 'entrain' 2>> <<run Msg.add('notice', L.unlockTrain, 1);>> <<run sv.tech.enTrees.pushUnique("training")>> <<run setup.batchUnlockNodes();>> <</dialogue>>
<<dialogue>> @bg event/bloodmoon-night.webp Crimson moonlight dyes the valley a blinding, glaring red. Inside the temple, the Planar Rift lets out a violent roar, its normally gentle liquid stars turning into a blood-red whirlpool. The air feels heavy enough to suffocate you. [you]: 〖Holy crap... moon's completely red. Feels... suffocating. Just staring at it makes my heart feel squeezed by a cold hand.〗 [you]: What's going on, Leah? Why’s the sky looking like a murder scene? It's just like the day I crossed over. The rift is going absolutely nuts! [leah]: Blood Moon. Magic veins of this world are bulging, nearing their critical limit. Do not attempt any summoning today. Risk is beyond your imagination. [leah]: The magic storm will completely shred any soul imprint you try to link. Raging spatial turbulence will instantly tear your mental bridge apart. Your mind will warp. What you summon won't be allies, but rogue void monsters that will tear this temple to pieces. [you]: 〖Mind warped? Pulling void monsters straight into the temple? No way! Came here to survive, not to have my brain turned to mush by some eldritch beast. Summoning is officially closed today!〗 [leah]: Smart choice. Last time the sky showed this omen, I hijacked that massive energy overload to drag your soul to this plane. Result was perfect... except the overload instantly leveled everything in the valley, leaving a massive charred crater where the estate used to stand. Wanna test your luck today? [you]: 〖Blew the baron's valley to pieces and called it "perfect"?! This goddess is a walking nuke! No wonder Gavin and Raymond looked like they saw a ghost whenever they talked about the blast!〗 [you]: You're a public safety hazard, Leah! What other trouble does this Blood Moon bring besides blowing up mountains? [leah]: Chain reactions. Demons get drunk on this magic radiation. Aggression spikes, and the threat doubles. Pray they stay in their camp today. Also, because of the magical surge, today's harvest will eat up far more energy, but whatever you gather is more likely to jump up a tier. [you]: How does this even happen? What's the science... I mean, the principles behind this? [leah]: Periodic, multi-year unstable overflow of the world's magic grid. Happened many times in history, always bringing disasters. This cycle's overflow is exceptionally severe, worst on record. [you]: Since the Blood Moon is so dangerous, why did you summon me during one? Plan sounds riddled with holes. [leah]: Beggars can't be choosers. Cross-planar soul tracking needs extremely harsh conditions. When they align, no time to care about the Blood Moon. Or rather, it was the extreme magic surge of the Blood Moon that cracked the world barriers. Without that gap, your soul would still be rotting in your boring cubicle. Wasn't a choice—it was the only key. [you]: 〖So I crossed over purely because of a "voltage overload"? Fine. Better than making PPTs for life.〗 [you]: Got it. No messing around, no summoning today. I’ll have Gavin double the guards on the walls for watch duty. [leah]: Go. Prep your blades. This crimson shadow is endless, and the world is watching. <</dialogue>>
<<dialogue>> [catmaid]: 【Stellafall’s gold eyes widen, pointing at the sky, voice buzzing with excitement.】 Master! A massive bird in the sky! Come quick! @item event/npcs/griffin.png [catmaid]: 【Stellafall flicks her tail, pointing to the horizon.】 Told you! Look, it’s swooping in! [butler]: 【Raymond steps forward, tone formal.】 My Lord, it’s a griffin knight from the Empire. We must greet him with respect. @item clear [griffinknight]: 【Gregory dismounts his griffin, armor clanking, eyes sharp as a hawk.】 Baron Wilderen, first time we’ve met, I reckon. Your father spoke of your years in the North. 【He pauses, tone tinged with relief.】 When your town fell to the demons, I feared for you. Seems the old Lord’s spirit kept you safe. [griffinknight]: 【Gregory’s voice drops, urgent.】 Name’s Gregory, Captain of the 3rd Griffin Squadron. The Dark Legion’s massing nearby—scouts say they’ll hit the Mountain Pass in 3 days. Get those defenses ready! 【His eyes glint with a friend’s concern.】 Your father was a friend, so I’m here to give you a heads-up: the pass is a choke point. Use it right, and you’ll stop those demonic claws. [griffinknight]: 【Gregory’s brow furrows, tone hardening.】 One more thing: the Empire’s demanding war taxes. Your family owes 100,000 gold. Don’t take it personal—times are dire, and the Empire’s on its last legs. [you]: 【Your eyes widen, voice shaky.】 What? 100,000 gold? 【You turn to Raymond, tone urgent.】 Butler, we’ve only got 2,000 gold, right? [butler]: 【Raymond bows his head, tone grave.】 You’re correct, my Lord. Just 2,000 gold. 【He sighs.】 We fled in haste—that’s all we could carry. [butler]: 【Raymond looks up, gaze earnest.】 Sir Gregory, you and the late Lord were close. For his sake, could you grant us time? He hoped you’d watch over his heir. With our lands gone, we’re barely surviving here. [griffinknight]: 【Gregory gives a wry chuckle, a flicker of warmth in his eyes.】 Raymond, you old fox, always with the smooth talk. 【His tone sharpens.】 Imperial rules don’t bend, but demons are a bigger threat than taxes. I’ll give you three months—hold the pass and survive first! Don’t expect soft words, Baron Wilderen. Show me some of your father’s grit! [butler]: 【Raymond bows slightly, tone grateful.】 Thank you for your mercy, Sir Gregory! My Lord, please express your gratitude. [you]: 【You take a deep breath, tone solemn.】 Thank you, Sir Gregory. 〖I’ll focus on the defenses and figure out the rest.〗 [griffinknight]: 【Gregory nods, vaulting onto his griffin, eyes blazing.】 Not bad—tougher than those pampered nobles. The demons will strike in 3 days—don’t let the pass fall! [griffinknight]: 【Gregory’s eyes scan the temple, voice low.】 Your father said Hawkridge Vale holds secrets even His Majesty values. Don’t let me down, Baron. 【He tightens the reins, tone stern.】 I’m off; scouting demons ain’t a picnic. One tip: steer clear of Beastfolk. [griffinknight]: 【He shouts, voice ringing.】 Glory to the Empire, and bones to the dust! @item event/npcs/griffin.png [catmaid]: 【Stellafall flicks her tail, tone teasing.】 Master, stop staring—the griffin’s gone! Why’re you frozen? @item clear [you]: 【You snap out of it, rubbing your face.】 What a presence. Captain, how do you measure up to him? [captain]: 【Garvin bows his head, tone mixed with awe and self-deprecation.】 Sir Gregory hails from military nobility. His father was a legendary armsmaster; his mother, the imperial alchemist. He earned the title ’Wing of the Empire’ by crushing Beastfolk wolfriders at Blackstone Gorge with seven griffins. My Lord, I couldn’t even handle a young griffin, let alone him. [you]: 【You frown, tone heavy.】 The Empire’s on its last legs if elites like him are sent as messengers. 〖I’d better start building defenses now.〗 <<SetFlag 'griffinknightflag' 1>> <<SetFlag 'enbastion' 2>> - Prepare Defenses => Yes - Maybe Later => End == Yes <<goto 'Battle_UI'>> <</dialogue>>
<<dialogue>> [griffinknight]: 【Gregory stands by his griffin, armor glinting in the sunset, eyes heavy.】 Baron Wilderen, we meet again. [griffinknight]: I’ve flown over countless lands, seen towns burn, lords fall. Do you know how heavy these words are? [griffinknight]: 【His tone softens, laced with approval.】 You’re struggling, but you’ve outlasted most lords. [griffinknight]: Grow stronger, Baron Wilderen; maybe you’ll save the Empire. Paying the tax wasn’t easy. We’ll meet again. [you]: 【You take a deep breath, eyes resolute.】 I won’t let you down, Sir Gregory. <<drop $storage "gold" $domain.wartax[0]>> <<set $domain.wartaxdeadline = 15 >> <<SetFlag 'wartaxs' false>> <<run $domain.wartax.shift()>> <</dialogue>>
<<dialogue>> [griffinknight]: 【Gregory stands by his griffin, armor glinting coldly, eyes stern yet tinged with regret.】 Baron Wilderen, this pains me. [griffinknight]: You’ve failed as a lord. The Empire reclaims your lands. @leave all Gregory vaults onto his griffin, not looking back, and soars away. Hawkridge Vale falls silent. -You Failed => Event_Badend == Event_Badend <<run Dialog.create("", "fullpop fit");>> <<run Dialog.wikiPassage('Event_Badend');>> <<run Dialog.open();>> <</dialogue>>
<<dialogue>> [griffinknight]: 【Gregory stands by his griffin, armor gleaming in the morning light, eyes tinged with approval.】 Baron Wilderen, we meet again. You kept your word—I’m pleased. [griffinknight]: 【He pats his griffin, tone brief but firm.】 See you in two weeks. Good luck. [you]: 【You watch Gregory’s retreating figure, shaking your head with a wry smile.】〖Even in another world, gold rules all.〗 <<drop $storage "gold" $domain.wartax[0]>> <<set $domain.wartaxdeadline = 15 >> <<run $domain.wartax.shift()>> <</dialogue>>
<<dialogue>> <<run $chars.dataMap.get("mirael").img = "atlas/unique/mirael_lock.webp">> The seventh day of battle ended with a victory that hardly felt like one. Mountain Pass was scarred by fire and shattered stone. Broken weapons lay among the dead, and the remnants of the Dark Legion had finally withdrawn into the night. As the Lord led the search through the abandoned enemy encampment, the soldiers discovered a hidden prison beneath the command tent. Inside were dozens of human captives. And among them was something no one expected to find. A demon. A young Shadow Whisperer sat chained against the far wall. Her bat-like wings had been severed at the roots. Dried blood stained the ragged remains, and her pale body was covered with bruises and old wounds. Two small horns emerged from her tangled dark hair, while crimson eyes remained lowered beneath the flickering torchlight. She did not struggle. She did not beg. She simply waited. As though she had already accepted whatever fate awaited her. @bg atlas/unique/mirael_lock.webp [butler]: 【Raymond steps forward, one hand resting firmly against his sword.】 My Lord, stop. Whatever that creature claims to be, she is still a servant of the Dark Legion. We cannot know what kind of deception lies beneath that appearance. [captain]: 【Garvin studies the prisoner carefully, his voice restrained but tense.】 We found her inside the Legion's prison, separated from their soldiers and locked together with human captives. Her wings were deliberately severed. She doesn't appear to have fought back. [you]: 【You stare at the prisoner in silence, weighing the evidence rather than the appearance.】 Why was a demon imprisoned by her own army? Her bat-like wings were cruelly severed, her pale, wounded form clad in a tattered dark gown. Her crimson eyes flickered in the torchlight, tiny demonic horns half-hidden in disheveled hair. She hung her head, as if awaiting her doom in despair. [mirael]: 【Mirael whispers, voice trembling, crimson eyes lifting slightly.】 Noble Lord, I am Mirael, a succubus... I served the Dark Legion against my will. They tore my wings and meant to drag me to their stronghold for execution. Please, shelter me! [you]: 【You frown, eyes stern, tone probing.】 Shelter you? Your kind slaughtered my men and nearly razed this stronghold! Why should I trust a demon’s plea? [mirael]: 【Mirael lifts her head, red eyes brimming with tears, voice earnest.】 I’ve taken no lives, Lord! I was tasked only with charm and scouting, and for defying orders, I was punished. The Dark Legion is merciless—I’d rather serve you and forsake that bloody abyss! [butler]: 【Raymond glares, tone fierce.】 Lies! My Lord, a demon’s tears are but a mask! Her words are sweet poison leading to damnation! [mirael]: 【Mirael gazes at you, voice sultry yet tinged with despair.】 He misunderstands my heart... Lord, you bear a heavy burden, yet your eyes hide solitude. Let me ease it, if only... with a moment’s warmth. [captain]: 【Garvin steps back uneasily, voice shaking.】 My Lord, her gaze unsettles the heart! My resolve wavers... she’s the devil incarnate! [you]: 【You narrow your eyes, tone hard, probing deeper.】 Mirael, your honeyed words don’t erase your demonic blood. Prove your worth, or I’ll cast you out! [mirael]: 【Mirael rises slowly, chains clinking, breath warm.】 I can bring you hope, Lord... My body can repay the blood debt, my offspring can breathe life into your stronghold. Let me serve as your maid, loyal until my soul fades to dust. [butler]: 【Raymond trembles with rage, voice shaking.】 Blasphemy! My Lord, this fiend’s offer is a pit of corruption! How can you let her taint the Wilderen name? [mirael]: 【Mirael chuckles softly, voice dreamlike, gaze profound.】 Butler, your loyalty is noble, yet blind to the heart’s cravings... Touch my hand, Lord, and feel my truth. My heart lies open to you. [you]: 【You hesitate, eyes wavering, heart conflicted.】 Your words are poison, Mirael... I cannot let the pass fall to my weakness! [mirael]: 【Mirael hums softly, eyes blazing like stars, the air shivering.】 Then let me offer you a glimpse of dreams, Lord... Close your eyes, see me serving as your maid, your stronghold thriving, my children fighting for you... This is no poison—it’s salvation. [captain]: 【Garvin steps back, voice urgent with fear.】 My Lord! She’s weaving demonic illusions! Break free of her spell! [mirael]: 【Mirael’s fingers brush your arm, warm as fire, voice a whisper.】 Ignore their fears, Lord. Your heart already knows the answer... Let me be your maid, your secret weapon. Accept me, guardian of Wilderen. [butler]: 【Raymond shakes his head in despair, voice heavy.】 The old Lord would never abide such corruption! My Lord, your will is our final bastion! [you]: 【You breathe heavily, tone wry yet cautious.】 Very well, Mirael, your visions are captivating. I accept your terms—stay as my maid. But betray me, and you’ll regret ever crossing this world! [mirael]: 【Mirael smiles, red eyes fathomless, tone enigmatic.】 You won’t be disappointed, Lord... My service will bloom in the whispers of the night. [captain]: 【Garvin mutters, eyes filled with worry.】 May the Empire’s light protect us... Her charm has seeped into every corner of the stronghold. [butler]: 【Raymond sighs, voice heavy as stone.】 Wilderen’s fate hangs by a thread... My Lord, may your choice be without regret. Mirael’s chains are unbound, her red eyes flickering in the torchlight. Guards whisper, morale rises with her allure, but the Dark Legion’s wrath seems kindled... [you]: 【You smirk inwardly, eyes scanning Mirael, heart wary.】 〖I’ve seen seduction tricks in movies far cleverer than this. I’ll play along to see her game—true or false, I won’t be the one to lose.〗 <<run $chars.dataMap.get("mirael").img = "atlas/unique/mirael_maid.png">> <<SetFlag 'miraelflag' 1>> - Take her in => okay ==okay <<run Msg.notify("[Mirael] Joined")>> <<AddMonster $chars.dataMap.get("mirael")>> <<SetFlag 'miraelflag' 2>> <<goto 'MainHall_UI'>> <</dialogue>>
<<dialogue>> [you]: 【You smile at Mirael.】Mirael, the time has come to fulfill your promise! [mirael]: 【Mirael’s crimson eyes glint, tone light.】Right here? No problem. <<run $chars.dataMap.get("mirael").img = "atlas/unique/mirael_preg0.png">> [mirael]: 【She sheds her dark gown, revealing her pale, scarred body, voice sultry.】Please be gentle, Lord, let me get used to your body... This is my first time. @item event/unique_nsfw/mirael0507144800.png [you]: 【You blink, tone surprised yet probing.】What...? This is nothing like I imagined! And how did your wounds heal so fast? [mirael]: 【Mirael chuckles softly, crimson eyes gleaming, tinged with regret.】A succubus's first time, with her charm magic? Unbeatable. Sadly, severed wings stole all my power. But your essence can still heal me, Master. Please... help me recover more~ @item event/unique_nsfw/mirael0507151415.png [you]: 【You narrow your eyes, tone cautious, mind calculating.】This is crucial information. I plan to summon more succubi—I need to know everything about them. [mirael]: 【Mirael draws closer, breath warm, voice solemn.】Yes, my Lord, I will give you a thousand offspring. [you]: 【You raise an eyebrow, smirking inwardly.】Probably simpler than you think. <<run $chars.dataMap.get("mirael").img = "atlas/unique/mirael_maid.png">> <<CardPop $chars.dataMap.get("you") $chars.dataMap.get("mirael") 1>> <<SetFlag 'miraelflag' 4>> <<run setup.pairOnce($chars.dataMap.get("mirael"), $chars.dataMap.get("you"))>> <<pickup $storage "Basic_Infuser_Core" 1>> <<run setup.TechTreeEngine.unlockNodeById("infuser1");>> <<run setup.TechTreeEngine.unlockNodeById("feline1", true);>> <<run setup.TechTreeEngine.unlockNodeById("canine1", true);>> <<run $chars.dataMap.get("mirael").calcAttrInfo("bond.vl", 1)>> <<redo "summon">> <<goto 'Summon_UI'>> <</dialogue>>
<<dialogue>> The energy infuser hums beside the bed, its arcane glow illuminating Mirael’s pale skin and crimson eyes. She reclines, her maid’s gown slightly loosened, exuding seductive yet trusting allure. [you]: 【You watch Mirael.】Mirael, how’s it feel? I just pumped thirty days of pregnancy into you—bet that’s gotta be... something, right? [mirael]: 【Mirael chuckles softly, crimson eyes glinting, hand gently caressing her abdomen.】My Lord, words fail to capture the delight... My belly feels so full, suffused with a satisfying warmth—no discomfort, only pleasure, like I’m floating on air~ [you]: 【You raise an eyebrow, tone surprised.】Floating, huh? Haha, sounds like you’re loving it. Good to know you’re not feeling off—I was worried this thing might be too intense. The ceramic angel figurine flickers, Leah’s telepathic voice ethereal and commanding. [leah]: Lord, the energy infuser drains vast reserves of my divine power, yet it is a necessity for these apocalyptic times, designed to hasten gestation. As the goddess of fertility, I ensure the mother’s safety—safer than mortal pregnancy, with greater benefits. [leah]: 【Leah’s voice deepens, laced with mystery.】Upon birth, the offspring enters the Planar Rift for summoning, bypassing the perils of delivery. In time, I shall unlock early birth and extended gestation, each safe and uniquely potent. But my divine power is finite—use it wisely. [you]: 【Your eyes widen, muttering in awe, mind reeling.】〖Holy crap, Leah, you fertility goddess, you’re the real deal! Playing with childbirth like this? Turbo mode, Planar Rift, early and extended options... it’s like coding new features. Wild stuff!〗 [mirael]: 【Mirael leans closer, breath warm, smile seductive.】My Lord, your shock is adorable... I find this blissful sensation far more enchanting than any demon world magic. Don’t you agree? [you]: 【You cough, scratching your head to mask embarrassment.】Ahem, yeah, pretty... enchanting, alright. You rest up, I need to process this "god-tier maneuver." [leah]: 【Leah’s voice turns commanding, laced with warning.】Lord, forget not the looming apocalypse. This gestation ensures offspring with the strongest parental traits, avoiding the unpredictability of Planar Rift summoning. The infuser is potent but drains my power dearly—I’ve suppressed temple gestation to conserve it. Use the summoning array against the Dark Legion! Mirael’s gestation ritual concludes under the bedroom’s arcane light, Leah’s divine act bringing new hope to Hawkridge Barony. You muse, energy allocation needs more caution. <<SetFlag 'miraelflag' 6>> <<run $chars.dataMap.get("mirael").calcAttrInfo("bond.vl", 1)>> <</dialogue>>
<<dialogue>> @item atlas/unique/mirael_preg7.png Mirael leans on the bedroom’s cushioned chaise, crimson eyes glinting, hand gently caressing her swollen belly, her maid’s gown clinging to her curves, breath quick yet laced with a seductive smile. [mirael]: My Lord, I feel... it’s coming soon! This belly’s so full it might burst, but there’s no pain—just... pure bliss~ Is this not the goddess’s miracle? [catmaid]: 【Stellafall’s cat-eyes widen, tail flicking as she leans close to Mirael, curiously touching her belly, then glancing at her own flat abdomen, ears twitching.】 Whoa, Mirael, your belly’s huge! Like you stuffed a giant pumpkin in there! Mine’s so tiny... How’s this even possible? How can a little tummy stretch like that? [leah]: 【The ceramic angel figurine glows with golden light, Leah’s voice ethereal and commanding, carrying a guiding tone.】 You escort Mirael to the Planar Rift. The ritual is prepared, the time is now. Fear not, I shall ensure all proceeds flawlessly. You support Mirael as Stellafall follows, her cat tail swaying, eyes brimming with curiosity and tension. The group traverses the temple’s corridors, arriving at the Planar Rift, its edges pulsing with purple arcane light, the air humming with energy. [leah]: 【Leah’s figurine’s light flaring as a summoning circle materializes on the ground, runes swirling.】 You have Mirael step into the summoning circle, and then you stand back and witness. The ritual begins! @item atlas/unique/mirael_preg0.png Mirael steps into the summoning circle, purple light rising from her feet, enveloping her. Suddenly, a dazzling golden beam bursts from her abdomen, surging into the Planar Rift, which trembles with a low roar. As the light fades, Mirael’s belly returns to its pre-pregnancy state, and she touches it in astonishment. <<run $chars.dataMap.get("mirael").img = "atlas/unique/mirael_preg0.png">> [mirael]: 【Mirael’s crimson eyes gleam, her smile radiant, voice buoyant.】 My Lord! I didn’t feel a thing! It’s like... nothing even happened! This is incredible! Goddess’s amazing! <<run $chars.dataMap.get("mirael").img = "atlas/unique/mirael_maid.png">> <<GetBirth "mirael">> Unbelievable! The Planar Rift flashes, and a new succubus steps forth, tail swaying, her smile seductive yet friendly. She waves at you, voice warm. New succubus: Hey there~ I’m the new girl! Lord, everyone, thanks for summoning me! This place looks lively—I can’t wait to join the fun! [you]: 〖I’ve definitely been isekai’d, not dreaming—my brain could never invent something this absurd.〗 <</dialogue>>
<<dialogue>> [catmaid]: 【Stellafall’s cat ears twitch, tail flicking faster as she leans toward the new succubus, voice stunned yet thrilled.】 Whoa! Another sister?! Mirael, your belly was *huge* a second ago, and now you’ve popped out this gorgeous lady?! That’s way too fast! You and Mirael lock eyes, lips curling into subtle smiles, silent but sharing a knowing glance. [leah]: 【Leah’s voice speaking directly to your mind.】 Lord, this ritual was a triumph. Mirael gains vast experience, and the new ally boasts exceptional talents, proof of my mastery over fertility. Let this dispel your doubts—use this method often, and victory against the Dark Legion is assured! [you]: 【You nod, glancing at the new succubus and Mirael, inwardly stunned but feigning calm.】 Holy—Leah, that was next-level! Alright, I gotta plan how to make the most of this trick! 〖This fertility goddess is straight-up cheating...〗 <<run $chars.dataMap.get("mirael").calcAttrInfo("bond.vl", 1)>> <</dialogue>>
<<dialogue>> War drums rumble faintly in the distance. <br>Guard Captain bursts in, clad in battle-worn armor, his face grim. [captain]: My Lord, get up! <br>The demons are massing at the Mountain Pass! <br>Scouts just reported—hordes of those cursed fiends, ready to charge any moment! <br>We’ve gotta hold the pass today, or we’re done for! <br>Throw on your armor and get to the pass interface—check the defenses now, we’re out of time! <<SetFlag 'battleday'>> <<set $battleInfo = {type:"weekly", day: $times.total, id: "defendId"} >> <<if $times.total === 8>>[you]: Finally. I was starting to get bored. <br>Sound the alarm, Captain. <br>Let them come—I’ve been waiting for a chance to test our new defenses.<</if>> <<if $times.total === 8>>=> End<</if>> - Go to the Pass => Yes - Maybe Later => End == Yes <<goto "Battle_UI">> <</dialogue>>
<<dialogue>> [promaid]: Good morning... My... Lord. <br>A merchant caravan has arrived today. <br>Would you like to inspect it? - Check It Out => Yes - Maybe Later => End == Yes <<goto 'Market_UI'>> <</dialogue>>
<<dialogue>> Erin approaches, head lowered, her tone soft yet nervous. [promaid]: Good morning... My... Lord. <br>The Silvertail Clan’s caravan just arrived, restocking the black market. <br>Miss Hizuki says… there are some fine goods this time, and you should definitely take a look. @leave all Hizuki strides into the hall. [foxmaid]: Hmph, my lord, <br>this mistress’s caravan delivered fresh goods right on time! <br>Miss them, and you’ll regret it! <br>So, checking them out, or too busy? - Check It Out => Yes - Maybe Later => End == Yes <<goto 'Market_UI'>> <</dialogue>>
<<dialogue 'MainHall_UI'>> [captain]: Morning, My Lord. <br>Today, <<set _humanpd = random((PopSystem.getTotalPop() /10), (PopSystem.getTotalPop() /5))>><<= _humanpd>> peasants from our old lands seek refuge. <br>Your orders? - Accept all => Yes - Refuse => End == Yes <<run PopSystem.autoSettle("human", _humanpd)>><<redo tag 'topbar'>> <</dialogue>>
<<dialogue>> [headmaid]: Good morning, My Lord. <br>A Slaver Caravan selling Beastfolk arrived. <br>Shall we check their stock? - Check It Out => Yes - Maybe Later => End == Yes <<goto "Slaver_UI">> <</dialogue>>
<<dialogue>> [captain]: 【Garvin strides forward, tense and urgent.】 My Lord, a lizardfolk seeks audience outside the camp, claiming to be an envoy of the Thornscale Clan! Her gaze is chilling, no friend, I wager. Shall we admit her? [you]: 【You frown, puzzled.】 〖This world’s full of weirdos! What’s a lizardfolk want now?〗 Let her in, Garvin, but keep guards close. [captain]: 【Garvin nods, tone wary.】 As you command, my Lord! But take heed—outsiders can’t be trusted! That tail... it moves like a serpent! [scalemaid]: 【Sariya steps forward, scales glinting in the torchlight, tone calm but probing.】 Soft-skin human, I am Sariya Thornscale of the Thornscale Clan, here for my people. You are the lord of this valley, foe of demons? [you]: 【You nod, eyes cautious.】 〖Soft-skin? That’s a weird one!〗 I’m the Lord. You didn’t come all this way to talk about the weather, did you? What’s your business? [scalemaid]: 【Sariya’s lips curl faintly, with dry humor.】 The weather? Soft-skins have curious minds. My clan has withdrawn to toxic swamps. The demons’ Blood Poison drives soft-skins mad, yet it cannot touch us. [you]: 【Your eyes spark with curiosity.】 Sounds like you’re tough. Cold blood? Immune to the Blood Poison? [scalemaid]: 【Sariya nods, faintly proud.】 Cold blood, a gift from the Mother of Wetlands. Blood Poison holds no sway, letting us tread demon lands to spy their ways. Yet, the swamp starves. We need allies. [you]: 【You frown, tone probing.】 So you’re here for a deal? What do you offer, and what’s the cost? [scalemaid]: 【Sariya’s golden eyes glow, staring at you, voice low.】 My Eyes of Prophecy see... you are no common soul, stranger from afar, your fate veiled. An ancient stone speaks of a hidden path in this valley, perchance a key to defy demons. [you]: 【You force calm, heart racing.】 〖Those eyes are like X-rays! Does she know I’m a transmigrator?〗 A hidden path? Worth a shot, but you say it’s just a prophecy—sure it’s real? [scalemaid]: 【Sariya’s tone is steady, gaze sharp.】 Prophecies never lie, yet its trace must be sought. I seek to remain here, to find the path. What are your terms? [you]: 【You ponder, tone cautious.】 〖The path sounds promising, but outsiders are tricky—need a backup plan.〗 A path could help us all, so you can stay. But with demons around, humans are wary of outsiders, might cause trouble. How about... posing as my maid? Easier to move around nearby. [scalemaid]: 【Sariya smirks, tail stiffening.】 A maid? Soft-skins have odd notions. Very well, for the path, I’ll play your... Scale-Maid. Expect no tea-serving. [you]: 【You grin, easing the tension.】 〖Scale-Maid? That’s a killer title!〗 Scale-Maid? Creative! Tea’s someone else’s job, just focus on the path. If you find anything good, I want a cut! [scalemaid]: 【Sariya’s tail flicks, tone dryly humorous.】 A cut? Soft-skins are greedy. We shall see. If the path appears, we both gain. Do not disappoint me, Lord. @item atlas/unique/scalemaid_maid.png Sariya dons a maid’s garb, remaining in Hawkridge Vale as the ’Scale-Maid.’ Her keen golden eyes watch the Lord’s deeds, seeking to affirm her prophecy of the ’Stranger from Afar’; meanwhile, she roams, hunting traces of the ancient path foretold by the stone. Campfolk whisper of the new Scale-Maid, fond of exploration, oft glimpsed ’twixt crags and ruins. <<run $chars.dataMap.get("scalemaid").img = "atlas/unique/scalemaid_maid.png">> <</dialogue>>
<<dialogue>> [you]: 【You grin at Sariya.】 〖She looks ready to dig through the valley!〗 Sariya, how’s the Scale-Maid gig? Any luck with that hidden path? [scalemaid]: 【Sariya’s lips curl, tone confident.】 Soft-skin Lord, the path lies close! My Eyes of Prophecy sense the stone’s aura. Once I scour these crags, victory is mine! <</dialogue>>
<<dialogue>> [you]: 【You look at Sariya with concern.】 〖She looks like she lost her tail!〗 Sariya, any progress on the path? Don’t wear yourself out—maid outfits aren’t for digging. [scalemaid]: 【Sariya sighs, tone heavy.】 Soft-skin, the path’s trace eludes me... Could the stone’s prophecy err? My heart’s troubled—spare me your words. <</dialogue>>
<<dialogue>> [you]: 【You tease Sariya playfully.】 〖She looks like she’s solving a cosmic riddle!〗 Sariya, daydreaming outside the temple? Found that path yet? [scalemaid]: 【Sariya frowns, tone cool.】 Soft-skin Lord, the path stays hidden... The stone’s guidance grows vague, as if veiled by strange forces. My mind’s troubled—leave me be. <</dialogue>>
<<dialogue>> [you]: 【You call out to Sariya.】Sariya! [scalemaid]: 【Sariya steps forward, tail twitching, tone wary.】Soft-skin Lord, I’ll speak plainly. This temple hall sees new faces oft, their origins unvoiced. Are you hiding aught from me? [you]: 【You feign surprise, teasing back.】〖This lizard lady’s sharp!〗Ho, Sariya, do your Eyes of Prophecy double as spy lenses? The newcomers... not from the path, rest easy. [scalemaid]: 【Sariya narrows her eyes, snorting.】Not the path? Soft-skin excuses convince no one. I serve my kin and prophecy—your secrets shan’t elude my gaze! [you]: 【You smile mysteriously, lowering your voice.】〖I sound like a cult leader!〗Alright, Sariya, I’ll let you in on a big secret. It involves... a goddess. Join, and you’re bound for life. Dare to try? [scalemaid]: 【Sariya’s tail stiffens, tone resolute.】A goddess? Soft-skin tricks, no doubt. Yet, for my kin and to prove the prophecy, I’ll brave the risk! Speak—what’s this secret? [you]: 【You point to the altar, dramatically.】〖This sounds way too mystical!〗Bold choice! The secret lies at the Planar Rift—a ’Summoning Rite.’ But, Sariya, there’s no turning back. [scalemaid]: 【Sariya’s eyes widen, teeth gritted.】Planar Rift? Summoning Rite? Soft-skin, your mystic ploys nearly fooled me! Yet, even at life’s cost, I’ll see it through! [you]: 【You wave a hand, tone casual.】〖She thinks I’m offing her?〗Easy, it’s not that serious! Just a little rite, show you something wild. [scalemaid]: 【Sariya snorts, tail flicking.】Hmph, soft-skin flippancy irks me. Lead on—I’ll see what secrets this rite unveils! [you]: 【You grin slyly, heading to the rift.】Heh, keep up, Sariya! <<set $summon.slots = [null, null]>> <<goto 'Summon_UI'>> Guided by you, Sariya, fraught with doubt yet loath to interrupt, follows to the Planar Rift. [you]: 【You gaze at Sariya, tone probing.】〖Will this lizard lady agree? Hope she doesn’t bite!〗Sariya, the temple’s Summoning Rite needs you... through coupling to invoke the Goddess of Fertility’s blessing. Are you willing? [scalemaid]: 【Sariya’s tail stiffens, golden eyes narrowing, tone wary.】Coupling? Soft-skin audacity knows no bounds! If this rite aids my kin and defies demons, I’ll try... but if you deceive, my tail shall pierce your heart! [you]: 【You feign ease, pointing to the rift.】〖She’s more riled than I expected!〗Easy, Sariya! It’s the Goddess’s will—I must plant the seed of life within you to awaken the rift. No turning back. Are you sure? [scalemaid]: 【Sariya grits her teeth, tone stern.】Soft-skin, your words jest, yet bear divine aura. For the Thornscale Clan, I’ll yield to the rite! Act swiftly, lest I regret! <<set $summon.slots = [$chars.dataMap.get("you"), $chars.dataMap.get("scalemaid")]>><<redo "summon">> You lead Sariya to the Planar Rift, and the rite begins. The rift hums, torchlight glints off her scales, and as you couple, the life-force surges within her, awakening the Goddess’s power. <<run $chars.dataMap.get("scalemaid").img = "atlas/unique/scalemaid_preg0.png">> @item event/unique_nsfw/scalemaid0625044114.webp [scalemaid]: 【Sariya’s breath quickens, golden eyes gleaming, tone angry yet awed.】Soft-skin! You... truly roused divine power! I feel life burgeoning within, as if the Goddess watches! @item event/unique_nsfw/scalemaid0625044114.webp [you]: 【You respond calmly, hiding a smile.】〖This lizard lady really knows how to freak out!〗Feel it? That’s the Goddess’s blessing. Focus, Sariya—the stronger the life-force, the better the rite! @item clear The rite ends, the rift’s glow fades, and Sariya emerges, scales shimmering, breath uneven, as if savoring the divine afterglow. [scalemaid]: 【Sariya licks her lips, tone dryly humorous.】Hmph, soft-skin, you didn’t deceive... This rite is truly queer, life-force surging like a tide. Call me oft—I shan’t mind another try. [you]: 【You shake your head, teasing back.】〖This lizard lady’s addicted!〗Hooked on the rite, eh? Don’t forget, Scale-Maid, your main job’s the path! <<run $chars.dataMap.get("scalemaid").img = "atlas/unique/scalemaid_maid.png">> <<SetFlag 'scalemaidflag' 1>> <<CardPop $chars.dataMap.get("you") $chars.dataMap.get("scalemaid") 1>> <<run setup.pairOnce($chars.dataMap.get("scalemaid"), $chars.dataMap.get("you"))>> <<run $chars.dataMap.get("scalemaid").calcAttrInfo("bond.vl", 1)>> <<redo "summon">> <</dialogue>>
<<dialogue>> [headmaid]: 【She places a steaming teacup on your desk, lowering her voice to a discreet whisper.】 My Lord, the woodsmen brought back an odd rumor from Dawnveil Lake today. They swear they spotted a creature—half woman, half fish—basking on the rocks. [you]: Half-fish? In Dawnveil Lake? An aquatic creature in an inland mountain crater... Fetch my cloak, I'll go inspect the shore myself. <<ToggleMap "vale">> @bg event/siren/mermaid-1.png The waters of Dawnveil Lake sit silent beneath the afternoon mist. Resting on a rock near the shore is a delicate creature—brown hair pinned with shell clips over her finned ears, sleek pale-blue skin glistening like pearls, forearm fins, and a smooth, horizontal tail. [you]: 【You step forward gently, keeping your hands visible.】 Peace, little one. I won't harm you. What's your name? Where did you come from? @bg event/siren/mermaid-2.png She jumps like a startled deer, pale blue eyes wide with pure fright. With a heavy splash, her silver-blue tail whips through the water as she vanishes into the dark depths. [you]: 【You sit on the rock, waiting for nearly an hour, but the water remains unbroken.】〖It's been over a month since I transmigrated to this realm, but the local ecology still throws me curveballs. Is she terrified, or does she just not speak the common tongue? Tsk... what a waste of a walk.〗 <</dialogue>>
<<dialogue>> <<ToggleMap "vale">> @bg event/siren/mermaid-3.png The next morning, you return to the shore of Dawnveil Lake. Behind a mossy boulder, a pair of wide, watery blue eyes peeks out cautiously. [???]: 【Her voice is barely a squeak over the water, trembling timidly.】 P-Please... don't hurt me with your metal sticks... [you]: 【You unbuckle your hunting dagger and set it on the grass, sitting cross-legged.】 No weapons, no threat. I'm just curious. Name’s Wilderen, the lord around here. What about you? @bg clear [mermaid]: 【She inches closer, her natural curiosity slowly mastering her fright.】 I... I'm Delphi! A scout from the Sirenia Tribe! A newly formed underwater cavern opened up in the seabed, and the current swept me straight into this lake! I just wanted to see what dry land looks like... I swear I didn't mean any harm! [you]: 【You listen intently, a sharp light flashing in your eyes.】〖Wait a second... Dawnveil Lake was created just a month ago when Goddess Leah's attempt to convert the Demon Portal backfired into an accidental explosion. It's an isolated crater fed purely by rain and mountain runoff. How does a month-old crater connect directly to the deep ocean miles away? Was that failed planar conversion messing with spatial dimensions beneath the lake bed?〗 [you]: 【You look closely at her smooth, scale-less blue skin and horizontal tail fluke.】 You look quite different from the traditional mermaid tales. Your skin has no fish scales, and your tail moves horizontally like a sea mammal. [mermaid]: 【She beams, fanning her forearm fins proudly.】 Oh! That's because I'm a Merrow! We're a branch of the Sirenia Tribe. We don't have heavy scales, so we can swim much faster! Though... the elders say us Merrows are only good for being low-ranking scouts... [you]: 【You offer a warm, welcoming smile.】 A Merrow, I see. Fascinating. Well, Delphi, staring at wet rocks all day must be dull. Would you like a tour of my estate? I can show you how land-dwellers live. [mermaid]: 【Her dorsal fin twitches with excitement.】 A tour? Really? But I can't walk on dirt! @bg event/siren/mermaid-5.png An hour later, two sturdy guards push a custom water-filled wooden tub on heavy iron wheels, wheeling Delphi through bustling market, and residential districts. [mermaid]: 【She splashes her tail merrily, touching dry stone walls and sniffing cooked spices in absolute wonder.】 This is incredible! Everything on land is so bright and strange! [mermaid]: 【As dusk falls, she dips back into the tub, looking earnest.】 I must return through the cave now. I need to report everything I saw to our Chieftain! She always says land-dwellers are greedy, dangerous monsters... [you]: 【You wave as your guards escort her back to the lake outlet.】 Safe travels, Delphi. Tell your chief our doors are open for friends. @bg clear [you]: 【You watch her cart retreat toward the lake, stroking your chin.】〖A Merrow scout driven by sheer curiosity... She's my golden ticket to bringing her whole tribe to the negotiation table.〗 <</dialogue>>
<<dialogue>> <<ToggleMap "vale">> @bg event/siren/mermaid-4.png On the third day, you return to the lake shore. Before you even reach the water, Delphi splashes her tail, popping up with a sunny wave. [mermaid]: 【She rests her arms on a rock, drooping her gill-ears slightly.】 I talked to the Chieftain... She scolded me for hours. She said humans are crafty land-apes with sharp iron sticks and shouldn't be trusted. [you]: 【You maintain your dignified lordly composure, smiling faintly.】 Did she lock you away to stop you from seeing me, then? [mermaid]: 【She perches higher onto the flat rock, her dorsal fin twitching in excitement.】 Nope! In fact... she assigned me to monitor this valley full-time! Wants me watching your every move. So... what am I supposed to do now? Just stalk you from the reeds? @bg clear [you]: 【You step closer, offering her a hand with an amused smirk.】 Stalking sounds exhausting. How about a proper job? Technically, you've been mine from the very start. [mermaid]: 【She blinks her pale blue eyes in confusion, tilting her head.】 Huh? Since when? I've only known you for three days! [you]: 【You tap her shoulder gently, a subtle wink in your eye.】 It's right in your species name: Mer-maid. You've got 'maid' written all over you. So from today on, you're my official valley attendant. [mermaid]: 【She bursts into a sunny laugh, her gill-ears fluttering merrily.】 Mer-maid?! Pfft—that's hilarious! A water-maid on dry land? I love it! I'm the Lord's official sea-maid now! [you]: 【You help her pull herself onto the grassy bank, guiding her as her tail awkwardly wriggles across the soft turf.】 Alright, my new maid, lesson one: land locomotion. Look at you... you look like a fish out of water, practically oozing despair. [mermaid]: 【She huffs, propping herself up on her forearms with a bright, defiant grin.】 Hey! I am not hopeless! Moving on grass is just... a very wiggly dance! Besides, dry land is a hundred times more fun than the dark ocean floor! They don't have those crunchy, sweet red spheres down there! [you]: 【You pull a crisp red apple from your pouch and hand it over, watching her eyes light up like stars.】 They're called apples, Delphi. And as long as you're my maid, you'll have all the apples you want. [mermaid]: 【She takes a big, noisy bite, her tail flapping happily against the grass.】 Yumm! Long live the Lord! I am never going back to chewing raw sea kelp! <<ToggleMap "barony">> <<run setup.mapPoint_enShow("sirenia_tidebreak")>> The location of the Sirenia Tribe has been marked on your world map. Delphi has officially joined your domain as your enthusiastic (and apple-obsessed) aquatic maid! <<AddMonster $chars.dataMap.get("mermaid")>> <<run Msg.notify("[<span>Delphi</span>] Joined")>> <<run $chars.dataMap.get("mermaid").calcAttrInfo("bond.vl", 1)>> <</dialogue>>
<div id="techUI-game-app-container"> <div id="techUI-main-stage"> <div class="techUI-tree-selector-tabs"> <div class="originbar-header"> <div class="originbar-title">TechTree</div> </div> <<TechUI_CatList>> </div> <div id="techUI-viewport"> <<TechUI_TreeShow>> </div> </div> </div> <<script>> setTimeout(function() { setup.TechTreeEngine.init(); }, 250); <</script>>
<div class="version-module" id="version-module"> <div class="version-header"> <span class="edition-badge edition-nsfw">Extended Edition</span> <span class="version-status" id="version-display">v0.0.0</span> </div> <div class="version-notice"> All CGs, Scenes, and Mod Features Enabled. </div> <div id="update-alert-area" style="display:none;"></div> <div id="browser-warning-area" style="display:none;"></div> <div class="version-btns"> <a class="version-btn" href="https://www.patreon.com/c/axiomnova" target="_blank" rel="noopener noreferrer">🔗 Patreon</a> <a class="version-btn" href="https://discord.gg/RNbvNpjXZW" target="_blank" rel="noopener noreferrer">💬 Discord</a> </div> <div class="version-environment" id="version-environment"></div> <div style="margin-top:6px;text-align:center;"> <a href="javascript:void(0)" id="force-reload-btn" style="text-decoration:underline;">🔄 Reload Game</a> </div> </div> <div class="version-module"> <h3 style="margin:0 0 12px 0;font-size:18px;border-bottom:1px solid #333;">Troubleshooting</h3> <p style="font-size:14px;color:#b0b0b0;margin:0 0 12px 0;">Seeing errors or missing UI? Try these three steps:</p> <ol style="margin:0;padding-left:20px;font-size:14px;text-align:left;"> <li style="margin-bottom:10px;"> <strong style="color:#fff;">Reload the game:</strong> Use the <em>Reload Game</em> button in the version info box. This can fix problems caused by an outdated cached build. </li> <li style="margin-bottom:10px;"> <strong style="color:#fff;">Start a new game:</strong> Do <em>not</em> load saves from a different game version—cross-version save compatibility is not supported. </li> <li style="margin-bottom:0;"> <strong style="color:#fff;">Reset Soul Data:</strong> This permanently deletes your achievements and other persistent meta-progression data. </li> </ol> </div> <<script>> $(document).ready(function() { var VERSION_JSON_URL = "https:\x2F\x2Faxiomnova.github.io/version.json"; function extractSemver(value) { if (!value) return null; var match = String(value).match(/v?(\d+\.\d+(?:\.\d+)?)/i); return match ? match[1] : null; } function parseVersion(value) { var version = extractSemver(value); if (!version) return null; var parts = version.split(".").map(Number); while (parts.length < 3) { parts.push(0); } return parts.slice(0, 3); } function compareVersions(a, b) { var v1 = parseVersion(a); var v2 = parseVersion(b); if (!v1 || !v2) return 0; for (var i = 0; i < 3; i++) { if (v1[i] > v2[i]) return 1; if (v1[i] < v2[i]) return -1; } return 0; } function isNewer(a, b) { return compareVersions(a, b) > 0; } var isNWjs = typeof nw !== "undefined" || ( typeof process !== "undefined" && process.versions && process.versions.nw ); var isOnline = !isNWjs && ( location.protocol === "http:" || location.protocol === "https:" ); var isIframe = false; try { isIframe = window.top !== window.self; } catch (error) { isIframe = true; } var userAgent = navigator.userAgent || ""; var isFirefox = /Firefox\/\d+/i.test(userAgent); var isEdge = /Edg\/\d+/i.test(userAgent); var isChrome = /Chrome\/\d+/i.test(userAgent) && !isEdge; var isChromium = /Chromium\/\d+/i.test(userAgent); var browserName = "Unknown"; var browserVersion = ""; if (isNWjs) { browserName = "NW.js"; browserVersion = process.versions.nw || ""; } else if (isFirefox) { browserName = "Firefox"; var firefoxMatch = userAgent.match(/Firefox\/([\d.]+)/i); browserVersion = firefoxMatch ? firefoxMatch[1] : ""; } else if (isEdge) { browserName = "Edge"; var edgeMatch = userAgent.match(/Edg\/([\d.]+)/i); browserVersion = edgeMatch ? edgeMatch[1] : ""; } else if (isChrome) { browserName = "Chrome"; var chromeMatch = userAgent.match(/Chrome\/([\d.]+)/i); browserVersion = chromeMatch ? chromeMatch[1] : ""; } else if (isChromium) { browserName = "Chromium"; var chromiumMatch = userAgent.match(/Chromium\/([\d.]+)/i); browserVersion = chromiumMatch ? chromiumMatch[1] : ""; } var environmentName = isNWjs ? "NW.js" : isIframe ? "Embedded / iframe" : "Web Browser"; var currentVersion = "0.0.0"; try { var changelog = Story.get("ChangeLogs"); if (changelog && typeof changelog.text === "string") { var versionMatch = changelog.text.match( /Version:\s*(v?\d+\.\d+(?:\.\d+)?)/i ); if (versionMatch) { currentVersion = extractSemver(versionMatch[1]) || "0.0.0"; } } } catch (error) { console.warn("Unable to read ChangeLogs:", error); } $("#version-display").text( currentVersion !== "0.0.0" ? "v" + currentVersion : "Unknown" ); $("#version-environment").text( browserName + (browserVersion ? " " + browserVersion : "") + " • " + environmentName ); if (isFirefox) { $("#browser-warning-area") .html( '<div class="browser-warning-box">' + '<strong>⚠️ Firefox detected.</strong><br>' + 'This game is primarily tested on Chromium-based browsers. ' + 'If you experience UI or rendering issues, please try Chrome or Edge.' + '</div>' ) .show(); } function reloadGame() { if (!isOnline) { window.location.reload(); return; } try { var url = new URL(window.location.href); url.searchParams.set( "_bww_reload", String(Date.now()) ); window.location.replace(url.toString()); } catch (error) { console.error("Reload failed:", error); window.location.reload(); } } $("#force-reload-btn") .off("click") .on("click", function() { var $button = $(this); $button .css({ "pointer-events": "none", "opacity": "0.6" }) .text("🔄 Reloading..."); reloadGame(); }); function showLatestReleaseNotice(latestVersion) { $("#update-alert-area") .html( '<div class="version-update-box">' + '<strong>Update Available: v' + latestVersion + '</strong><br>' + 'Visit Patreon or Discord for the latest release.' + '</div>' ) .show(); } function showStaleBuildNotice(itchVersion) { $("#update-alert-area") .html( '<div class="version-update-box">' + '<strong>⚠️ Outdated Build Detected</strong><br>' + '<small>Your browser may still be running an older cached build. ' + 'Use <em>Reload Game</em> above to load the current itch.io build.</small>' + '<br><small>Running: v' + currentVersion + ' • Itch.io: v' + itchVersion + '</small>' + '</div>' ) .show(); } function checkVersionInfo() { if (!isOnline) return; var requestUrl = VERSION_JSON_URL + "?_t=" + Date.now(); $.getJSON(requestUrl) .done(function(data) { if (!data) return; var latestVersion = extractSemver(data.latest); var itchVersion = extractSemver(data.itch); if (!latestVersion || !itchVersion) { console.warn( "Invalid version.json: missing latest or itch version." ); return; } if (isNewer(itchVersion, currentVersion)) { showStaleBuildNotice(itchVersion); } if (isNewer(latestVersion, currentVersion)) { showLatestReleaseNotice(latestVersion); } }) .fail(function(xhr, status, error) { console.warn( "Version check failed:", status, error ); }); } checkVersionInfo(); }); <</script>>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<if setup.currentView>> <<includeplus setup.currentView>> <<else>> <<includeplus MainHall>> <</if>> </div> <<include "UI_RightBar">> </div> </div>
<style> .blood-moon-container { background: radial-gradient(circle at center, #150505 0%, #080202 100%); border: 2px solid #6b1414; box-shadow: inset 0 0 15px rgba(255, 0, 0, 0.15), 0 0 25px rgba(0, 0, 0, 0.8); padding: 16px; width: 1024px; text-align: center; color: #dfc5c5; position: relative; } .blood-moon-title { font-size: 20px; color: #bb0000; letter-spacing: 3px; text-transform: uppercase; margin-bottom: 8px; } .blood-moon-body { font-size: 16px; line-height: 1.6; color: #9c7373; font-style: italic; letter-spacing: 0.5px; } </style> <div class="blood-moon-container"> <div class="blood-moon-bg">[img[setup.ImagePath + 'event/bloodmoon-night.webp']]</div> <div class="blood-moon-title">The Blood Moon Ascends</div> <div class="blood-moon-body"> Sky bleeds as the crimson orb rises, painting the earth in silent ruin. Disaster stirs in the dark. An unspeakable dread crawls into the hearts of all who dare look upon the scarlet moon. Destiny draws near. </div> </div>
<<cacheaudio "track01" setup.AudioPath+"track01.ogg">> <<waitforaudio>> <<cacheaudio "track02" setup.AudioPath+"track02.ogg">> <<cacheaudio "track03" setup.AudioPath+"track03.ogg">> <<cacheaudio "track04" setup.AudioPath+"track04.ogg">> <<cacheaudio "track05" setup.AudioPath+"track05.ogg">> <<cacheaudio "track06" setup.AudioPath+"track06.ogg">> <<cacheaudio "track07" setup.AudioPath+"track07.ogg">> <<cacheaudio "track08" setup.AudioPath+"track08.ogg">> <<cacheaudio "track09" setup.AudioPath+"track09.ogg">> <<cacheaudio "track10" setup.AudioPath+"track10.ogg">> <<cacheaudio "track11" setup.AudioPath+"track11.ogg">> <<cacheaudio "track12" setup.AudioPath+"track12.ogg">> <<cacheaudio "track13" setup.AudioPath+"track13.ogg">> <<cacheaudio "track14" setup.AudioPath+"track14.ogg">> <<cacheaudio "track15" setup.AudioPath+"track15.ogg">> <<cacheaudio "track16" setup.AudioPath+"track16.ogg">> <<cacheaudio "track17" setup.AudioPath+"track17.ogg">> <<cacheaudio "track18" setup.AudioPath+"track18.ogg">> <<cacheaudio "track19" setup.AudioPath+"track19.ogg">> <<cacheaudio "track20" setup.AudioPath+"track20.ogg">> <<cacheaudio "track21" setup.AudioPath+"track21.ogg">> <<cacheaudio "track22" setup.AudioPath+"track22.ogg">> <<cacheaudio "track23" setup.AudioPath+"track23.ogg">> <<cacheaudio "track24" setup.AudioPath+"track24.ogg">> <<cacheaudio "track25" setup.AudioPath+"track25.ogg">> <<cacheaudio "joyous_002" setup.AudioPath+"joyous_002.ogg">> <<cacheaudio "joyous_003" setup.AudioPath+"joyous_003.ogg">> <<cacheaudio "joyous_008" setup.AudioPath+"joyous_008.ogg">> <<cacheaudio "joyous_013" setup.AudioPath+"joyous_013.ogg">> <<cacheaudio "joyous_018" setup.AudioPath+"joyous_018.ogg">> <<cacheaudio "girl_022" setup.AudioPath+"girl_022.ogg">> <<cacheaudio "girl_024" setup.AudioPath+"girl_024.ogg">> <<cacheaudio "ruins_001" setup.AudioPath+"ruins_001.ogg">> <<cacheaudio "relaxed_010" setup.AudioPath+"relaxed_010.ogg">> <<cacheaudio "theme_015" setup.AudioPath+"theme_015.ogg">> <<cacheaudio "theme_morning" setup.AudioPath+"first_light.ogg">>
<<set _nowweek = Math.max(Math.trunc($times.total / 7), 1)>> <<BattleWaves _nowweek>> <<set $battle.captures = generateCaptures(_nowweek)>> <<if _nowweek === 19>><<run Msg.add('notice', "Defensive content is currently polished through Week 19. To keep the gameplay running beyond that point, enemies will scale in strength and wave count. You can always choose to skip these encounters if you wish.")>><</if>> <<include "Battle_Script">> <div class="battle-panel"> <div class="topbattle"> <div class="game-container"> <canvas id="gameCanvas"></canvas> <<do tag "gridArea">> <div class="grid-area" id="gridArea"> <<set _slotmax = $buildingEffects.slots.bastion >> <<for _row = 0; _row < 3; _row++>> <<for _col = 0; _col < 7; _col++>> <<set _slotindex = _col * 3 + _row >> <<set _unit = $buildings.hawkridgepass.bastion[_slotindex]>> <<capture _row, _col, _slotindex _unit>> <<if _unit>> <<if _unit.id.startsWith("wall")>><<set _classgrid = "grid-cell wall">> <<elseif _unit.id.startsWith("gate")>><<set _classgrid = "grid-cell gate">> <<else>><<set _classgrid = "grid-cell tower">> <</if>> <<else>> <<set _classgrid = "grid-cell">> <</if>> <div @class="_classgrid" @data-row="_row" @data-col="_col"> <<if !(_unit) && (_slotindex < _slotmax)>> <<link "Build">> <<SimpleUI "Battle_Deploy" 90>> <</link>> <</if>> <<if (_unit) && (_slotindex < _slotmax)>> <<set _slotimg = setup.bdUnitData[_unit.id].img>> <<set _slotlv = "lv"+ _unit.lv >> <<if _unit.id.startsWith("tower")>> <<set _arrowConfig = getUnitData(_unit.id, _unit.lv, 'arrowRef') >><<set _unitConfig = setup.arrow[_arrowConfig.type]['levels'][_arrowConfig.lv]['behavior']>> <<set _towerlv = "lv"+ _unit.lv >> <<csslink "<<widthframe _slotimg _slotlv 4>>" "towerlist">> <<SimpleUI "Battle_Deploy" 90>> <</csslink>> <<else>> <<csslink "<<widthframe _slotimg _slotlv 4>>" "towerlist">> <<SimpleUI "Battle_Deploy" 90>> <</csslink>> <</if>> <</if>> </div> <</capture>> <</for>> <</for>> </div> <</do>> </div> </div> <div class="battle-container"> <div class="skillbook-container"></div> <div class="battle-side section-panel"> <div class="section-title">Dark Legion</div> <div class="battle-stat-bar"> <span class="stat-name">BossHP:</span> <div class="battle-bar-container"> <div class="battle-bar health-bar" id="attacker-health-bar"></div> </div> <span class="battle-stat-value" id="attacker-health-text"></span> </div> <div class="battle-stat-bar"> <span class="stat-name">Attack:</span> <div class="battle-bar-container"> <div class="battle-bar attack-bar" id="attacker-attack-bar"></div> </div> <span class="battle-stat-value" id="attacker-attack-text"></span> </div> <div class="enemies-stats-container"> <div class="enemies-stats" id="enemies-stats"></div> </div> <div class="section-panel"> <div class="section-title"> <span>Prisoner Convoy</span> </div> <div class="capture-grid-list"> <<do tag "capture">> <<if Flag('battleday')>> <<for _i, _capture range $battle.captures>> <<capture _capture>> <div @class="'hostage-cell ' + (_capture.stat =='Rescued'?'rescued':'')"> <div class="hostage-content"> <div class="hostage-type">_capture.type</div> <div class="hostage-status">_capture.stat</div> </div> </div> <</capture>> <</for>><</if>> <</do>> </div> </div> <div class="battle-flyout-wrapper" id="battle-log-widget"> <button class="battle-flyout-trigger"> <span class="battle-flyout-icon">📜</span> <span class="battle-flyout-latest" id="battle-log-latest">Awaiting orders...</span> <span class="battle-flyout-toggle">▲</span> </button> <div class="battle-flyout-panel battle-flyout-up"> <div class="section-title">Battle Log</div> <div class="battle-log" id="battle-log" style="height: 150px; overflow-y: auto;"> <div class="log-entry"> <h3>Heed This Warning, Noble Commander!</h3> <p>Ensure your defenses are fully prepared before pressing the "Start Battle" button!</p> </div> </div> </div> </div> </div> <div class="mid-space"> <div class="section-panel"> <div class="section-title">Battle Command</div> <div class="battle-battle-btns"> <<link "<div id='deploy-battle'>🛡️Deploy Defenses</div>">><<SimpleUI "Battle_Deploy" 90>><</link>> <<if Flag('battleday')>> <<link "<div id='start-battle'>⚔️Start Battle</div>">> <<if !$battle.gameStarted>> <<dropall $battlebag>> <<addclass ".UItopbar" "hidden">> <<addclass ".RightBar" "hidden">> <<addclass ".bdoperate" "hidden">> <<replace "#start-battle">>In Battle<</replace>> <<run UIBar.hide().stow();>> <<remove "#tiredbattle">> <<EventRadarHide>> <<run startBattle()>> <</if>> <</link>> <div id="tiredbattle"> <<HoverTip "Skip defense. No loot will be awarded; best for players who prefer to bypass the tower defense.">> <<link "💔Skip Defense">> <<SetFlag 'battleday' false>> <<set $battle.demonattackdeadline = 7 >> <<WonRun>> <<goto "Battle_UI">> <</link>> <span>❓</span> <</HoverTip>> </div> <<else>> A Calm Day <</if>> </div> </div> <div class="section-panel" style="display: flex;flex: 1;padding: 13px 1rem;flex-direction: column;"> <div class="section-title">Aiming Mode & Speed</div> <div class="aim-mode-selector"> <label><<radiobutton "$battle.defense.aimMode" "nearest" autocheck>><span>Nearest</span></label> <label><<radiobutton "$battle.defense.aimMode" "focusBoss" autocheck>><span>FocusBoss</span></label> <label><<radiobutton "$battle.defense.aimMode" "oneToOne" autocheck>><span>OneToOne</span></label> </div> <div class="speed-capsule" id="battle-speed-panel"> <button class="speed-nav-btn" id="btn-speed-down" title="Decelerate (Left Arrow)">⏪</button> <button class="speed-main-btn" id="btn-speed-toggle" title="Play/Pause (Spacebar)">▶ 1.0x</button> <button class="speed-nav-btn" id="btn-speed-up" title="Accelerate (Right Arrow)">⏩</button> </div> </div> </div> <div class="battle-side section-panel"> <div class="module-grid-2col"> <div class="stat-container"> <div class="stat-label"> <span>Defense</span> <span id="defender-fortification-text">80 / 100</span> </div> <div class="battle-stat-bar"> <div class="defense-bar" id="defender-fortification-bar"></div> </div> </div> <div class="stat-container"> <div class="stat-label"> <span>Morale</span> <span id="defender-morale-text">60 / 100</span> </div> <div class="battle-stat-bar"> <div class="morale-bar" id="defender-morale-bar"></div> </div> </div> </div> <div class="section-panel"> <div class="section-title">Battle Command</div> <div class="generals-grid"> <<do tag "Commander">> <<set _generalinfo = $battle.defense.generals["Commander"]>> <<set _general = getMonsterByID(_generalinfo.id)>> <<HoverTip "_generalinfo.desc" >> <div class="general-card general" data-name="Commander"> <div class="general-portrait" id="generalbutton"> <<if _general>><<HeadImageByID _generalinfo.id>><</if>> <div class="general-info"> <div class="general-name"><<= _general?.name || "<span>+</span>">></div> <div class="general-role">Commander</div> </div> </div> <button class="action-button" data-action="troop">Gallant Charge</button> </div> <</HoverTip>> <</do>> <<do tag "Warden">> <<set _generalinfo2 = $battle.defense.generals["Warden"] >> <<set _general = getMonsterByID(_generalinfo2.id)>> <<set _generalWarden = `<div class="general-portrait" id="generalbutton"> <<if _general>><<HeadImageByID _generalinfo2.id>><</if>> <div class="general-info"> <div class="general-name"><<= _general?.name || "<span>+</span>">></div> <div class="general-role">Warden</div> </div> </div>` >> <<HoverTip "_generalinfo2.desc" >> <div class="general-card general" data-name="Warden"> <<link _generalWarden>> <<run Dialog.create("", "filterresults")>> <<run Dialog.wiki("<<GeneralFilter 'Warden'>>")>> <<run Dialog.open()>><</link>> <button class="action-button" data-action="repair-defense">Repair Defense</button> </div> <</HoverTip>> <</do>> <<do tag "Guardian">> <<set _generalinfo3 = $battle.defense.generals["Guardian"] >> <<set _general = getMonsterByID(_generalinfo3.id)>> <<set _generalGuardian = `<div class="general-portrait" id="generalbutton"> <<if _general>><<HeadImageByID _generalinfo3.id>><</if>> <div class="general-info"> <div class="general-name"><<= _general?.name || "<span>+</span>">></div> <div class="general-role">Guardian</div> </div> </div>` >> <<HoverTip "_generalinfo3.desc" >> <div class="general-card general" data-name="Guardian"> <<link _generalGuardian>> <<run Dialog.create("", "filterresults")>> <<run Dialog.wiki("<<GeneralFilter 'Guardian'>>")>> <<run Dialog.open()>><</link>> <button class="action-button" data-action="rush-enemies">Rush Enemies</button> </div> <</HoverTip>> <</do>> <<do tag "Bard">> <<set _generalinfo4 = $battle.defense.generals["Bard"] >> <<set _general = getMonsterByID(_generalinfo4.id)>> <<set _generalBard = `<div class="general-portrait" id="generalbutton"> <<if _general>><<HeadImageByID _generalinfo4.id>><</if>> <div class="general-info"> <div class="general-name"><<= _general?.name || "<span>+</span>">></div> <div class="general-role">Bard</div> </div> </div>` >> <<HoverTip "_generalinfo4.desc" >> <div class="general-card general" data-name="Bard"> <<link _generalBard>> <<run Dialog.create("", "filterresults")>> <<run Dialog.wiki("<<GeneralFilter 'Bard'>>")>> <<run Dialog.open()>><</link>> <button class="action-button" data-action="quick-heal">Quick Heal</button> </div> <</HoverTip>> <</do>> <<do tag "Ranger">> <<set _generalinfo5 = $battle.defense.generals["Ranger"] >> <<set _general = getMonsterByID(_generalinfo5.id)>> <<set _generalRanger = `<div class="general-portrait" id="generalbutton"> <<if _general>><<HeadImageByID _generalinfo5.id>><</if>> <div class="general-info"> <div class="general-name"><<= _general?.name || "<span>+</span>">></div> <div class="general-role">Ranger</div> </div> </div>` >> <<HoverTip "_generalinfo5.desc" >> <div class="general-card general" data-name="Ranger"> <<link _generalRanger>> <<run Dialog.create("", "filterresults")>> <<run Dialog.wiki("<<GeneralFilter 'Ranger'>>")>> <<run Dialog.open()>><</link>> <button class="action-button" data-action="assassinate">Assassinate</button> </div> <</HoverTip>> <</do>> <<do tag "Strategist">> <<set _generalinfo6 = $battle.defense.generals["Strategist"] >> <<set _general = getMonsterByID(_generalinfo6.id)>> <<HoverTip "_generalinfo6.desc" >> <div class="general-card general" data-name="Strategist"> <div class="general-portrait" id="generalbutton"> <<if _general>>[img[setup.ImagePath + _general.head]]<</if>> <div class="general-info"> <div class="general-name"><<= _general?.name || "<span>+</span>">></div> <div class="general-role">Lord</div> </div> </div> <button class="action-button" data-action="capture">Captive Rescue</button> </div> <</HoverTip>> <</do>> </div> <div class="casting-bar-container"> <div class="casting-label">Command Charge</div> <div class="casting-bar"> <div class="casting-progress" id="defender-charging-bar"></div> </div> </div> </div> <div class="battle-flyout-wrapper" id="battle-stats-widget"> <button class="battle-flyout-trigger"> <span class="battle-flyout-icon">📊</span> <span class="battle-flyout-latest">Rescue Camp & Combat Statistics & Attributes</span> <span class="battle-flyout-toggle">▲</span> </button> <div class="battle-flyout-panel battle-flyout-up"> <div class="section-panel" style="margin:0; border:none; background:transparent;"> <div class="section-title"><<HoverTip "Successfully Rescued Monster Girls.">>Rescue Camp<</HoverTip>> <div class="rescue-count"> </div> </div> <div class="sanctuary-grid" id="defender-captives"> <div class="sanctuary-slot filled">Rescued</div> <div class="sanctuary-slot"></div> </div> </div> <div class="module-grid-2col"> <div class="section-panel" style="margin:0; border:none; background:transparent;"> <div class="section-title">Knight's Valor</div> <div class="module-grid-2col" id="general-stats"> </div> </div> <div class="section-panel" style="margin:0; border:none; background:transparent;"> <div class="section-title">Towers' Might</div> <div class="module-grid-2col" id="towers-stats"> </div> </div> </div> </div> </div> </div> </div> </div> <<set _FacHelp = "CodexEntry_Battle_DefenseCommand">>
<div class="keep-container"> <div class="keep-left-panel"> <<do tag "zone">> <<set _currenzone = $buildings["hawkridgepass"]["bastion"]>> <section class="keep-slots-section"> <h2 class="keep-section-title">Land Units</h2> <div class="keep-slots-grid" id="keepSlotsGrid"> <<for _index range 21>> <<set _currenunit = _currenzone[_index] >> <<capture _index _currenunit>> <<if _currenunit>> <<set _unit = $buildings.hawkridgepass.bastion[_index]>> <<set _slotimg = setup.bdUnitData[_unit.id].img>> <<set _slotlv = "lv"+ _unit.lv >> <<set _slotdeploy = `<div class="keep-slot has-building" @id="_index"> <div class="keep-slot-building"> <div><<widthframe _slotimg _slotlv 4>></div> <div class="keep-unit-label"> <div class="keep-slot-name"><<= getUnitData(_currenunit.id,"name")>></div> <div class="keep-slot-type"></div> </div> <div class="keep-slot-level"><<= _currenunit.lv>></div></div> </div>` >> <<link _slotdeploy>> <<set _showunit = _currenzone[_index] >> <<set _showzone = $buildings["hawkridgepass"]["bastion"]>> <<set _showindex = _index>> <<redo "unit">><<redo "build">> <<removeclass '.keep-slot' 'selected'>><<addclass `"#"+_index` 'selected'>> <</link>> <<elseif _index < $buildingEffects["slots"][_currenzone.id]>> <<link '<div class="keep-slot" style="background-color: var(--bg-body);" @id="_index"></div>'>> <<set _showindex = _index>> <<unset _showunit>> <<set _showzone = $buildings["hawkridgepass"]["bastion"]>> <<redo "unit build">> <<removeclass '.keep-slot' 'selected'>><<addclass `"#"+_index` 'selected'>> <</link>> <<else>> <<link '<div class="keep-slot unitlocked">🔒</div>'>><<run setup.Popup("<<UpgradeZoneById 'bastion'>>")>><</link>> <</if>> <</capture>> <</for>> </div> </section> <</do>> <section class="keep-selection-section"> <<do tag "build">> <<if (_showindex || _showindex==0) && _currenunit == null>> <h2 class="keep-section-title">Optional Units</h2> <div class="keep-building-selection" id="keepBuildingSelection"> <<set _chunits = Object.values(setup.bdUnitData) .filter(b => b.type === _currenzone.id)>> <<for _chunit range _chunits>> <<capture _chunit>> <<set _newsr = getUnitData(_chunit.id, 1, "cost") >> <<set _newbuildui = `<div class="keep-building-card" @id="_chunit.id"> <div class="keep-building-image"> <div class="keep-building-image-placeholder"><<showframe _chunit.img lv1 4>></div> </div> <div class="keep-building-info"> <div class="keep-building-name"><<= getUnitData(_chunit.id,"name")>></div> <div class="keep-building-type"><<= _chunit.class>></div> </div> </div>` >> <<link _newbuildui>> <<set _showunit = {"id": _chunit.id, "lv": 0} >><<redo "unit">> <<removeclass '.keep-building-card' 'selected'>><<addclass `"#"+_chunit.id` 'selected'>> <</link>> <</capture>> <</for>> <<else>> <</if>> <</do>> </section> </div> <<do tag "unit">> <div class="keep-right-panel"> <<if _showunit && (_showindex || _showindex==0)>> <<set _upgradecost = getUnitData(_showunit.id, (_showunit.lv + 1), "cost") >> <div class="keep-building-details" id="keepBuildingDetails"> <h2 class="keep-building-name"><<= getUnitData(_showunit.id,"name")>></h2> <div class="keep-building-type"></div> <p class="keep-building-description"><<if getZoneData(_currenzone.id,"desc")>><<= getZoneData(_currenzone.id,"desc")>><</if>></p> <<if _showunit.id.startsWith("tower")>> <div class="keep-resource-list"> <h3 class="keep-section-title">Arrow Stat</h3> <<set _arrowConfig = getUnitData(_showunit.id, (_showunit.lv || 1), 'arrowRef') >> <<set _unitConfig = setup.arrow[_arrowConfig.type]['levels'][_arrowConfig.lv]['behavior']>> <div class="keep-resource-grid"> <div class="keep-resource-item"> <span class="keep-resource-name">Type</span> <span class="keep-resource-value"><<= _unitConfig.type>></span> </div> <div class="keep-resource-item"> <span class="keep-resource-name">Level</span> <span class="keep-resource-value"><<= _unitConfig.lv>></span> </div> <div class="keep-resource-item"> <span class="keep-resource-name">Damage</span> <span class="keep-resource-value"><<= _unitConfig['physicalDamage']>></span> </div> <div class="keep-resource-item"> <span class="keep-resource-name">Proj. Speed</span> <span class="keep-resource-value"><<= _unitConfig['projectileSpeed']>></span> </div> <div class="keep-resource-item"> <span class="keep-resource-name">Fire Rate</span> <span class="keep-resource-value"><<= _unitConfig['fireRate'] +"/ s">></span> </div> </div> </div> <</if>> <div class="keep-resource-list"> <h3 class="keep-section-title"><<= _showunit.lv != 0 ? "Upgrade " :"Building ">>Cost</h3> <div class="keep-resource-grid"> <<if _showunit.lv < getUnitData(_showunit.id,"maxlv")>> <<for _res, _amount range _upgradecost>> <div class="keep-resource-item"> <span class="keep-resource-name"><<=_res>></span> <span class="keep-resource-value"><<=_amount>></span> </div> <</for>> <<else>> Maximum Level! <</if>> </div> </div> <h3 class="keep-section-title">Upgrade path</h3> <div class="keep-upgrade-box"> <table class="keep-upgrade-table"> <thead> <tr> <th style="width: 1rem;">Lv</th> <th style="width: 8.25rem;">Effect</th> <th>Cost</th> </tr> </thead> <tbody> <<for _i range getUnitData(_showunit.id,"maxlv")>> <<set _reallv = _i +1 >> <tr @class="(_reallv == _showunit.lv) ? 'keep-current-level' : ''"> <td><<= _reallv>><<if _reallv == _showunit.lv>><div id="lvlabel">curren</div><</if>></td> <td><<= EffectUtils.autoDesc(getUnitData(_showunit.id, _reallv, "effect"))>></td> <td> <<set _upgradecost = getUnitData(_showunit.id, _reallv, "cost") >> <<for _res, _amount range _upgradecost>> <span class="res-icon res-_res.type"><<=_res>> x<<=_amount>>,</span> <</for>> </td> </tr> <</for>> </tbody> </table> </div> </div> <div class="keep-actions"> <<if _showunit && _showunit.lv == 0>> <<link '<div class="keep-button keep-button-build">Build</div>'>><<BuildUnitByIndexId _showindex _showunit.id>><<redo "gridArea">><<if setup.isCurrentView("Battle")>><<run initTowers()>><<run recalculateBattleStats('build')>><</if>><</link>> <<elseif _showunit>> <<set _maxLv to getUnitData(_showunit.id, "maxlv")>> <<if _showunit.lv < _maxLv>> <<link '<div class="keep-button keep-button-upgrade">Upgrade</div>'>> <<UpgradeUnitByIndexId _showindex _showunit.id>><<redo "gridArea">><<if setup.isCurrentView("Battle")>><<run initTowers()>><<run recalculateBattleStats('upgrade')>><</if>> <</link>> <<else>> <div class="keep-button keep-button-upgrade" style="background:#333; cursor:not-allowed; opacity:0.6;">Maximum</div> <</if>> <<link '<div class="keep-button keep-button-demolish">Demolish</div>'>><<DemolishUnitByIndexId _showindex _showunit.id>><<redo "gridArea">><<if setup.isCurrentView("Battle")>><<run initTowers()>><<run recalculateBattleStats('demolish')>><</if>><</link>> <</if>> <<link '<div class="keep-button keep-button-cancel">Cancel</div>'>><<unset _showunit>><<unset _showindex>><<redo "build zone unit">><</link>> </div> <<else>> <</if>> </div> <</do>> </div>
<div class="examine-captive character-module"> <div class="content-area"> <div class="character-portrait"> <div class="character-image"> <<if def _examRace>> <<NewMonster _examRace>> <<else>> <<NewMonster>> <</if>> <<run setup.initBodyTraining($addmonster)>> <div class='GetE1img'><<ShowPortrait $addmonster>></div> <<set _CharSay = [ "Lord Baron, thank you for my rescue! I wish to serve in your lands!", "The demons’ chains are broken! Thanks to your timely rescue, I’m saved!", "Your valor will echo across the land! I’ll sing of Lord Baron’s deeds!", "Lord Baron saved me from demon claws! I’ll stay to repay you!", "The demon prison is broken, Lord Baron. I offer my loyalty and strength!" ].random() >> <div class="examine-captive-speech-bubble"> <<= _CharSay>> </div> </div> </div> <div class="character-controls"> <h2 class="controls-title">Captive Handling</h2> <div class="button-container"> <<link "<div class='control-button' title='Join: Welcome the monster girl into the temple for further actions.'>Join</div>">><<set $addmonster.mood.vl += 30 >><<AddMonster $addmonster>><<unset $addmonster>><<run setup.Popup.close()>><</link>> <<link "<div class='control-button' title='Shelter: Take the monster girl into the barony, counted as population but not actionable.'>Shelter</div>">><<run PopSystem.add($addmonster, "barony")>><<unset $addmonster>><<run setup.Popup.close()>><</link>> <<link "<div class='control-button' title='Release: Set free beyond the barony, left to fend for themselves.'>Release</div>">><<unset $addmonster>><<run setup.Popup.close()>><</link>> </div> <div class="description-area"><ul> <li>Join: Welcome the monster girl into the temple for further actions.</li> <li>Shelter: Take the monster girl into the barony, counted as population but not actionable.</li> <li>Release: Set free beyond the barony, left to fend for themselves.</li></ul> </div> </div> </div> <div class="attributes-area"> <div class="attributes-content"> <<Infocard $addmonster>> </div> </div> </div>
<<done>> <<script>> var canvas = document.getElementById('gameCanvas'); if (!canvas) { console.error('Canvas not found!'); } var BASE_WIDTH = 1920; canvas.width = BASE_WIDTH; canvas.height = 20 * 16; var ctx = canvas.getContext('2d'); var lastFrameTime = 0; let chargeCurrentTime = 0; var gravity = 0.5; var gridArea = document.getElementById('gridArea'); var rows = 3; var cols = 7; var gridSize = 4 * 16; var gridWidth = 28 * 16; var gridHeight = 12 * 16; var gridOffsetX = canvas.width - gridWidth; const buildingpara = State.variables.buildingEffects; var defensePara = { ...State.variables.battle.defense, name: "Defenders", isCharging: false, get maxfortitude() {return buildingpara.stats.defense;}, get maxMorale() {return Math.trunc((PopSystem.getTotalPop()) * 0.2);}, get troopAttack() {return (getMonsterByID(this.generals.Commander.id)?.str.lv || 0) + buildingpara.stats.attack;}, get troopArmor() {return (getMonsterByID(this.generals.Guardian.id)?.dex.lv || 0) + buildingpara.stats.armor;}, get troopMaxHp() {return (getMonsterByID(this.generals.Warden.id)?.sta.lv || 0) + buildingpara.stats.health;}, get troopRegen() {return (getMonsterByID(this.generals.Bard.id)?.cha.lv || 0) / 10 + buildingpara.stats.regen;}, get troopCrit() {return Math.clamp(((getMonsterByID(this.generals.Ranger.id)?.fer.lv || 0) + buildingpara.stats.crit), 0 , 50);}, get charge() {return Math.clamp((2000 - State.variables.chars.dataMap.get("you").wil.lv * 3 - buildingpara.stats.charge), 500 , 2000);}, get captivesCount() {return buildingpara.capacities.rescuecamp;}, towersBoost: { allFireRate: 1, allDamage: 1 }, captives: [], speed: 300, combatDuration: 10, isDeployed: false, isRetreating: false, currentSpeed: 0, lastAttackTime: 0, pushTime: 0, collidedEnemies: [] } let chargeAnimationTimer = null; let chargeCompleteTimer = null; window.resetChargingBars = function() { if (chargeAnimationTimer) clearTimeout(chargeAnimationTimer); if (chargeCompleteTimer) clearTimeout(chargeCompleteTimer); defensePara.isCharging = false; chargeCurrentTime = 0; $("#defender-charging-bar").stop(true, true).css({ width: "0%", transition: "none" }); hideActionButtons(); }; window.hideActionButtons = function() { $('.action-button').removeClass('ready'); }; window.showActionButtons = function() { try { $('.general-card').each(function() { const role = $(this).attr('data-name'); if (!role) return; const generalInfo = State.variables.battle.defense.generals[role]; const btn = $(this).find('.action-button'); if (generalInfo && generalInfo.id !== "") { btn.addClass('ready'); } else { btn.removeClass('ready'); } }); addLog(`Command Charge complete. Awaiting orders!`); if (typeof Msg !== 'undefined') Msg.notify("Command Charge Ready!"); } catch (e) { console.error("UI Update Error in showActionButtons:", e); $('.action-button').addClass('ready'); } }; window.startCharging = function() { if (sv.battle.gameEnded) return; if (chargeAnimationTimer) clearTimeout(chargeAnimationTimer); if (chargeCompleteTimer) clearTimeout(chargeCompleteTimer); defensePara.isCharging = true; chargeCurrentTime = 0; hideActionButtons(); const $bar = $("#defender-charging-bar"); if (!$bar.length) return; $bar.css({ width: "0%", transition: "none" }); }; window.executeAction = function(action) { if (State.variables.battle.gameEnded) return; resetChargingBars(); try { switch (action) { case "troop": { if (defensePara.isDeployed) { addLog("Cannot deploy: Lord’s Guard already deployed!"); return; } else if (defensePara.hp <= 0) { addLog("Cannot deploy: Lord’s Guard has fallen!"); return; } else { defenderTroop = new DefenderTroop(); defensePara.isDeployed = true; addLog("Lord’s Guard unleashes Gallant Charge, shattering the demonic line!"); addLog("Note: This skill is for survival only—extremely powerful and unbalanced. Use with caution!"); updateStats(); break; } } case "repair-defense": defensePara.fortitude = Math.min(defensePara.maxfortitude, defensePara.fortitude + 10); addLog(`Defenders urgently bolsters fortifications, gaining 10 fortitude!`); updateStats(); break; case "capture": { if (Math.random() < defensePara.captivesodds) { const emptyIndex = defensePara.captives.length; if (emptyIndex < defensePara.captivesCount) { defensePara.captives.push(true); function rescueNext(captures) { const index = captures.findIndex(item => item.stat === "Captured"); if (index !== -1) { captures[index].stat = "Rescued"; } return captures; }; rescueNext(State.variables.battle.captures); $.wiki('<<redo "capture">>'); $(`#defender-captives .sanctuary-slot`).eq(emptyIndex).addClass("filled"); addLog(`You’ve freed the monster girl from the demons’ clutches! She’s safe.`); } else { addLog(`You tried to rescue the monster girl, but all safe havens in Hawkridge Pass are full.`); } } else { addLog(`The rescue attempt failed. The demons overpowered your team, and the monster girl remains captive!`); } updateStats(); break; } case "quick-heal": defensePara.hp = Math.min(defensePara.troopMaxHp, defensePara.hp + 10); addLog(`Lord’s Guard swiftly recovers, gaining 10 hp!`); updateStats(); break; case "assassinate": { addLog(`Seize the moment when the enemy is distracted during their attack to assassinate their leader. If successful, the enemy Boss loses 50% of their health. Success rate equals the current critical hit chance.`); if ((100 * Math.random()) < defensePara.troopCrit) { const maxhp = getEnemyWithMaxHP(); maxhp.hp = maxhp.hp / 2; addLog(`Assassination Successful!`); } else { addLog(`Assassination Failed.`); } updateStats(); break;} case "rush-enemies": if (State.variables.enemies.rushed) { addLog("Enemies already hastened!"); return; } State.variables.enemies.waveInterval = Math.max(3, State.variables.enemies.waveInterval / 2); State.variables.enemies.rushed = true; addLog("Enemy waves hasten! Interval halved!"); updateStats(); break; } } catch (e) { console.error("Skill Execution Error:", e); } updateStats(); startCharging(); }; $(document).off("click", ".action-button").on("click", ".action-button", function(e) { e.preventDefault(); if (!$(this).hasClass('ready')) { return; } const action = $(this).attr("data-action"); executeAction(action); }); function updateState() { State.variables.battle.defense = defensePara; } function initArmies() { $("#defender-captives").empty(); for (let i = 0; i < defensePara.captivesCount; i++) { const isFilled = defensePara.captives[i] ? "filled" : ""; $("#defender-captives").append(`<div class="sanctuary-slot ${isFilled}"></div>`); } defensePara.hp = defensePara.troopMaxHp; defensePara.arrowsFired = 0; defensePara.totalDamageDealt = 0; defensePara.fortitude = defensePara.maxfortitude; defensePara.morale = defensePara.maxMorale; State.variables.battle.totalEnemiesKilled = 0; State.variables.battle.gameStarted = false; State.variables.battle.gameEnded = false; resetChargingBars(); hideActionButtons(); updateStats(); } initArmies(); function getBossHealth() { try { const bosses = enemies.filter(e => e.element.className.includes('boss') || e.element.className.includes('commander')); if (bosses.length > 0) { const mainBoss = bosses.reduce((prev, current) => (prev.hp > current.hp) ? prev : current); const maxHp = mainBoss.maxHp || State.variables.battle.attack.maxHealth; const pct = Math.max(0, mainBoss.hp) / maxHp * 100; $("#attacker-health-bar").css("width", pct + "%"); $("#attacker-health-text").text(Math.round(Math.max(0, mainBoss.hp)) + "/" + maxHp); } else { $("#attacker-health-bar").css("width", "0%"); $("#attacker-health-text").text("No Boss"); } } catch (e) { console.error("Boss HP UI Error:", e); $("#attacker-health-bar").css("width", "0%"); } } function updatefortitude() { $("#defender-fortification-bar").css("width", (Math.max(0, defensePara.fortitude) / defensePara.maxfortitude * 100) + "%"); $("#defender-fortification-text").text( Math.clamp(Math.trunc(defensePara.fortitude), 0, defensePara.maxfortitude) + "/" + defensePara.maxfortitude); } function updatemorale() { $("#defender-morale-bar").css("width", (Math.max(0, defensePara.morale) / defensePara.maxMorale * 100) + "%"); $("#defender-morale-text").text( Math.clamp(Math.trunc(defensePara.morale), 0, defensePara.maxMorale) + "/" + defensePara.maxMorale); } function updateStats() { $("#attacker-attack-bar").css("width", (100) + "%"); $("#defender-morale-bar").css("width", (Math.max(0, defensePara.morale) / defensePara.maxMorale * 100) + "%"); $("#defender-morale-text").text( Math.clamp(Math.trunc(defensePara.morale), 0, defensePara.maxMorale) + "/" + defensePara.maxMorale); $("#defender-fortification-bar").css("width", (Math.max(0, defensePara.fortitude) / defensePara.maxfortitude * 100) + "%"); $("#defender-fortification-text").text( Math.clamp(Math.trunc(defensePara.fortitude), 0, defensePara.maxfortitude) + "/" + defensePara.maxfortitude); updateState(); } function moraleRegen() { if (State.variables.battle.gameEnded) return; defensePara.morale = defensePara.morale + (10 / defensePara.charge); updatemorale(); } $(document).off("click", ".action-button").on("click", ".action-button", function(e) { e.preventDefault(); if (!$(this).hasClass('ready')) { return; } const action = $(this).data("action"); executeAction(action); }); function addLog(message) { const timestamp = new Date().toLocaleTimeString('en-US', { hour12: false }); const logEntry = document.createElement('div'); logEntry.className = 'log-entry'; logEntry.textContent = `[${timestamp}] ${message}`; const battleLog = document.getElementById('battle-log'); if (!battleLog || !logEntry) { console.warn('#battle-log none'); return; } battleLog.prepend(logEntry); if (battleLog.children.length > 50) { battleLog.lastChild.remove(); } $('#battle-log-latest').text(message); if (!$('#battle-log-widget').hasClass('is-open')) { $('#battle-log-latest').css('color', '#d4af37'); setTimeout(() => { $('#battle-log-latest').css('color', '#c0c0c0'); }, 500); } } function getTowerColRow(index) { let col = Math.floor(index / 3); let row = (index % 3) + 1; return { col: col, row: row }; } function getTowerIndex(col, row) { let clampedRow = Math.max(1, Math.min(row, 3)); return col * 3 + clampedRow - 1; } function Tower(index, towerId, lv, towerindex) { this.towerId = towerId; this.lv = lv; this.row = getTowerColRow(towerindex).row; this.col = getTowerColRow(towerindex).col; this.x = gridOffsetX + (this.col + 0.5) * gridSize; this.y = (this.row + 0.5) * gridSize; this.index = index; this.target = null; var arrowRef = getUnitData(towerId, lv, "arrowRef"); this.arrowConfig = setup.arrow[arrowRef.type].levels[arrowRef.lv]; this.fireRate = this.arrowConfig.behavior.fireRate; this.fireTimer = 0; } Tower.prototype.fire = function() { let dt = BattleTimer.dt; this.fireTimer += dt; var fireInterval = 1 / this.fireRate; if (this.fireTimer >= fireInterval) { this.fireTimer -= fireInterval; var targetEnemy = selectTarget(this, enemies, defensePara.aimMode); if (targetEnemy) { var dx = targetEnemy.x - this.x; var dy = targetEnemy.y - this.y; var distance = Math.hypot(dx, dy); var t = distance / this.arrowConfig.behavior.projectileSpeed; var arrow = arrowPool.getArrow(); if (arrow) { arrow.reset(this.x, this.y, dx / t, dy / t, this.arrowConfig, targetEnemy); defensePara.arrowsFired++; } } } }; var towers = []; window.initTowers = function () { let slotCount = buildingpara.slots.bastion; let slotsbastion = State.variables.buildings.hawkridgepass.bastion; towers = []; for (let i = 0; i < slotCount; i++) { let slot = slotsbastion[i]; if (slot?.id?.startsWith("tower")) { towers.push(new Tower(towers.length, slot.id, slot.lv, i)); } }; }; function Arrow() { this.x = 0; this.y = 0; this.vx = 0; this.vy = 0; this.active = false; this.params = null; this.isRebounding = false; this.target = null; }; var arrowPool = { pool: [], initialSize: 50, maxSize: 2000, activeCount: 0, init: function() { this.pool = []; for (var i = 0; i < this.initialSize; i++) { this.pool.push(new Arrow()); } this.activeCount = 0; }, getArrow: function() { for (var i = 0; i < this.pool.length; i++) { if (!this.pool[i].active) { this.activeCount++; return this.pool[i]; } } if (this.pool.length < this.maxSize) { var newArrow = new Arrow(); this.pool.push(newArrow); this.activeCount++; return newArrow; } console.warn("Arrow pool exhausted. Consider increasing maxSize."); return null; }, releaseArrow: function(arrow) { if (arrow.active) { arrow.active = false; this.activeCount--; } }, update: function(dt) { for (var i = 0; i < this.pool.length; i++) { if (this.pool[i].active) { this.pool[i].update(dt); } } }, draw: function() { for (var i = 0; i < this.pool.length; i++) { if (this.pool[i].active) { this.pool[i].draw(); } } } }; Arrow.prototype.reset = function(x, y, vx, vy, arrowConfig, target) { this.x = x; this.y = y; this.vx = vx; this.vy = vy; this.active = true; this.params = { behavior: arrowConfig.behavior, draw: arrowConfig.draw }; this.physicalDamage = arrowConfig.behavior.physicalDamage; this.isRebounding = false; this.target = target; }; Arrow.prototype.draw = function() { if (!this.active) return; ctx.fillStyle = this.params.draw.fillStyle; ctx.strokeStyle = this.params.draw.strokeStyle; ctx.lineWidth = this.params.draw.lineWidth; var angle = Math.atan2(this.vy, this.vx); ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(angle); this.params.draw.commands.forEach(command => { ctx.beginPath(); if (command.type === 'path') { command.points.forEach((point, index) => { if (index === 0) { ctx.moveTo(point.x, point.y); } else { ctx.lineTo(point.x, point.y); } }); if (command.closePath) ctx.closePath(); ctx.fill(); ctx.stroke(); } else if (command.type === 'arc') { ctx.arc(0, 0, command.radius, command.startAngle, command.endAngle); ctx.fill(); ctx.stroke(); } else if (command.type === 'image') { var img = new Image(); img.src = command.src; ctx.drawImage(img, -command.width / 2, -command.height / 2, command.width, command.height); } else if (command.type === 'text') { ctx.font = command.font; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(command.content, command.offsetX || 0, command.offsetY || 0); if (this.params.draw.strokeStyle && this.params.draw.lineWidth) { ctx.strokeText(command.content, command.offsetX || 0, command.offsetY || 0); } } }); ctx.restore(); }; Arrow.prototype.update = function(dt) { if (!this.active) return; var oldX = this.x; var oldY = this.y; var newX = this.x + this.vx * dt; var newY = this.y + this.vy * dt; if (newX < 0 || newX > canvas.width || newY < 0 || newY > canvas.height) { arrowPool.releaseArrow(this); return; } if (newY < 64 || newY > 256) { arrowPool.releaseArrow(this); return; } var hitEnemy = null; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var enemyWidth = enemy.width * 1.1; var enemyHeight = enemy.height * 1.1; var rx = enemy.x - enemyWidth / 2; var ry = enemy.y - enemyHeight / 2; if ( newX > rx && newX < rx + enemyWidth && newY > ry && newY < ry + enemyHeight ) { hitEnemy = enemy; break; } var t = this.lineIntersectsRect(oldX, oldY, newX, newY, rx, ry, enemyWidth, enemyHeight); if (t >= 0 && t <= 1) { hitEnemy = enemy; break; } } if (hitEnemy) { hitEnemy.hp -= this.physicalDamage; defensePara.totalDamageDealt += this.physicalDamage; hitEnemy.element.style.filter = 'brightness(2)'; setTimeout(function() { if (hitEnemy.element) hitEnemy.element.style.filter = ''; }, 100); arrowPool.releaseArrow(this); return; } this.x = newX; this.y = newY; }; Arrow.prototype.lineIntersectsRect = function(x1, y1, x2, y2, rx, ry, rw, rh) { var left = rx; var right = rx + rw; var top = ry; var bottom = ry + rh; var tValues = []; if (x1 !== x2) { var t = (left - x1) / (x2 - x1); if (t >= 0 && t <= 1) { var y = y1 + t * (y2 - y1); if (y >= top && y <= bottom) tValues.push(t); } } if (x1 !== x2) { var t = (right - x1) / (x2 - x1); if (t >= 0 && t <= 1) { var y = y1 + t * (y2 - y1); if (y >= top && y <= bottom) tValues.push(t); } } if (y1 !== y2) { var t = (top - y1) / (y2 - y1); if (t >= 0 && t <= 1) { var x = x1 + t * (x2 - x1); if (x >= left && x <= right) tValues.push(t); } } if (y1 !== y2) { var t = (bottom - y1) / (y2 - y1); if (t >= 0 && t <= 1) { var x = x1 + t * (x2 - x1); if (x >= left && x <= right) tValues.push(t); } } if (tValues.length === 0) return -1; var minT = tValues[0]; for (var i = 1; i < tValues.length; i++) { if (tValues[i] < minT) minT = tValues[i]; } return minT; }; function selectTarget(tower, enemies, mode) { const validEnemies = enemies.filter(e => e.hp > 0 && e.element.parentNode); if (validEnemies.length === 0) return null; let target = null; if (mode === 'focusBoss') { target = validEnemies.find(e => e.element.className.includes('commander')) || validEnemies.find(e => e.element.className.includes('boss')) || validEnemies.find(e => e.element.className.includes('elite')); if (!target) { target = validEnemies.reduce((prev, curr) => (curr.x > prev.x) ? curr : prev); } return target; } else if (mode === 'oneToOne') { if (tower.target && validEnemies.includes(tower.target)) { return tower.target; } const targetedByOthers = towers.map(t => t.target).filter(t => t !== null); const freeEnemies = validEnemies.filter(e => !targetedByOthers.includes(e)); target = freeEnemies.length > 0 ? freeEnemies[0] : validEnemies[0]; tower.target = target; return target; } else { let minDist = Infinity; for (let i = 0; i < validEnemies.length; i++) { let enemy = validEnemies[i]; let dist = Math.hypot(tower.x - enemy.x, tower.y - enemy.y); if (dist < minDist) { minDist = dist; target = enemy; } } return target; } } function Dropitem(id, x, y, config) { this.id = id; this.x = x; this.y = y; this.config = config; this.element = document.createElement('div'); this.element.className = 'item ' + id; const width = 30; const height = 30; this.element.style.width = `${width}px`; this.element.style.height = `${height}px`; this.element.style.left = `${x - width / 2}px`; this.element.style.top = `${y - height / 2}px`; const imagePath = setup.ImagePath; const imageFile = (id === "gold" ? "ui/coins.png" : "ui/chests.png"); this.element.style.background = `url(${imagePath}${imageFile}) no-repeat center`; this.element.style.backgroundSize = 'contain'; this.element.style.position = 'absolute'; this.element.style.zIndex = '1'; const container = document.querySelector('.game-container'); if (container) { container.appendChild(this.element); } var self = this; this.element.addEventListener('click', function() { self.pickup(); }); } Dropitem.prototype.pickup = function() { if (this.id.startsWith("skill")) { let skillid = this.id.slice(6); State.variables.skillbag[skillid] = (State.variables.skillbag[skillid] || 0) + 1; setup.SkillManager.renderSkillList(); const skillData = setup.SkillManager.skills[skillid]; const skillName = skillData ? skillData.name : "Unknown Skill"; addLog('Picked up skill: ' + skillName); } else { addLog('Picked up: ' + this.id + " x" + this.config.amount); } this.element.remove(); var index = dropitems.indexOf(this); if (index !== -1) { dropitems.splice(index, 1); } }; var dropitems = []; function Enemy(id) { const params = generateSpecificEnemy(id); if (!params) { console.error(`Enemy data for ${id} not found`); return; } const [idrace, idtype] = id.split('_'); this.id = id; this.hp = params.hp; this.maxHp = params.hp; this.movexspeed = params.speed; this.power = params.power; this.x = 0; this.y = Math.random() * 160 + 80; this.width = params.size; this.height = this.width; this.movetype= params.movetype || "linear"; this.movey= 0; this.moveyspeed= 0; this.phase= 'takeoff'; this.phaseProgress= 0; this.jumpy= this.y; this.nowheight = this.height; this.nowwidth = this.width; this.angle = 0; this.origin = "bottom center"; this.opacity = 1; this.display = "inline-block"; this.spriteDataVar = params.spriteData || 'enemySprite'; this.frameId = idrace; this.element = document.createElement('span'); this.hasReachedGate = false; this.damageTimer = 0; this.drops = params.drops; this.initRender = function() { const spriteData = setup.iconSpriteData[this.spriteDataVar || 'enemySprite']; const frame = spriteData?.frames?.[this.frameId]; if (!frame || !spriteData?.grid) { console.error(`Sprite data for ${this.spriteDataVar} or frame ${this.frameId} not found`); return; } const { rows, cols, frameWidth, frameHeight } = spriteData.grid; this.element.className = `${params.style} enemy-sprite`; this.element.style.position = 'absolute'; this.element.style.display = 'inline-block'; this.element.style.overflow = 'hidden'; this.element.style.width = `${this.width}px`; this.element.style.height = `${this.height}px`; this.element.style.transformOrigin = this.origin; this.element.style.transform = `translateZ(0)`; const img = document.createElement('img'); img.src = spriteData.img; img.style.position = 'absolute'; const totalImgWidth = cols * this.width; const totalImgHeight = rows * this.height; const offsetX = frame.col * this.width; const offsetY = frame.row * this.height; img.style.width = `${totalImgWidth}px`; img.style.height = `${totalImgHeight}px`; img.style.left = `-${offsetX}px`; img.style.top = `-${offsetY}px`; img.style.maxWidth = 'none'; img.style.margin = '0'; img.style.padding = '0'; img.style.imageRendering = 'auto'; this.element.appendChild(img); this.lastX = 0; this.lastY = null; this.lastMovey = 0; this.lastOpacity = 1; this.lastAngle = 0; this.lastdisplay = "inline-block"; this.lastorigin = "bottom center"; this.lastnowheight = this.height; this.lastnowwidth = this.width; this.dirty = true; }; this.initRender(); this.renderSprite = function() { if ( this.x === this.lastX && this.y === this.lastY && this.movey === this.lastMovey && this.opacity === this.lastOpacity && this.angle === this.lastAngle && this.display === this.lastdisplay && this.origin === this.lastorigin && this.nowheight === this.lastnowheight && this.nowwidth === this.lastnowwidth ) { return; } const elem = this.element; if (this.x !== this.lastX || this.nowwidth !== this.lastnowwidth) { elem.style.left = `${Math.round(this.x - this.nowwidth / 2)}px`; this.lastX = this.x; } if (this.y !== this.lastY || this.movey !== this.lastMovey || this.nowheight !== this.lastnowheight) { elem.style.top = `${Math.round(this.y - this.movey - this.nowheight / 2)}px`; this.lastY = this.y; this.lastMovey = this.movey; } if (this.opacity !== this.lastOpacity) { elem.style.opacity = this.opacity; this.lastOpacity = this.opacity; } if (this.display !== this.lastdisplay) { elem.style.display = this.display; this.lastdisplay = this.display; } if (this.origin !== this.lastorigin) { elem.style.transformOrigin = this.origin; this.lastorigin = this.origin; } if (this.nowheight !== this.lastnowheight || this.nowwidth !== this.lastnowwidth || this.angle !== this.lastAngle) { const scalex = this.nowwidth / this.width; const scaley = this.nowheight / this.height; elem.style.transform = `rotate(${this.angle}deg) scale(${scalex}, ${scaley}) translateZ(0)`; this.lastAngle = this.angle; this.lastnowwidth = this.nowwidth; this.lastnowheight = this.nowheight; } }; const container = document.querySelector('.game-container'); if (container) container.appendChild(this.element); else console.error('Game container not found!'); } window.sineLUT = (() => { const table = []; const resolution = 360; for (let i = 0; i < resolution; i++) { table[i] = Math.sin((i / resolution) * 2 * Math.PI); } return table; })(); Enemy.prototype.update = function() { let dt = BattleTimer.dt; let frameMult = dt * 60; if (defensePara.morale <= 0) { this.element.remove(); enemies.splice(enemies.indexOf(this), 1); return; } if (this.hasReachedGate) { this.damageTimer += dt; var damageInterval = 5 / this.movexspeed; if (this.damageTimer >= damageInterval) { if (defensePara.fortitude > 0) { defensePara.fortitude -= this.power; updatefortitude(); } else { defensePara.morale -= this.power; updatemorale(); } addLog('Gate sustained damage: power=' + this.power + ', gate.hp=' + defensePara.fortitude); this.damageTimer = 0; } if (this.hp <= 0) { this.element.remove(); enemies.splice(enemies.indexOf(this), 1); State.variables.battle.totalEnemiesKilled++; State.variables.battlebag.pickup("gold", this.power); if (this.drops) { this.drops.forEach(function(drop) { if (Math.random() < drop.probability) { if (drop.id.startsWith("skill")) { var config = {}; addLog('Dropped skill: ' + drop.id + " on the path"); dropitems.push(new Dropitem(drop.id, this.x, this.y, config)); } else { var config = { amount: drop.amount }; State.variables.battlebag.pickup(drop.id, drop.amount); addLog('Dropped ' + drop.id + " x" + drop.amount + " on the path and automatically picked up"); } } }, this); } else { console.error('No drops for enemy type:', this.id); } } return; } if (this.movetype == "linear") { this.x += this.movexspeed * frameMult; } else if (this.movetype == "float") { this.phaseProgress += (1/120) * frameMult; this.x += this.movexspeed * frameMult; const index = Math.trunc((this.phaseProgress % 2) * 180); this.movey = Math.trunc(16 * sineLUT[index]); } else if(this.movetype == "roll") { this.origin = "center center"; this.x += this.movexspeed * frameMult; const radius = this.width / 2; const dtheta = (this.movexspeed / radius) * 57 * frameMult; this.angle = (parseFloat(this.angle) + dtheta).toFixed(1); this.angle %= 360; } else if (this.movetype == "randomy") { this.phaseProgress += (1/30) * frameMult; this.x += this.movexspeed * frameMult; if (this.phaseProgress >= 1) { this.phaseProgress = 0; this.moveyspeed = 0.03 * (Math.random() < 0.5 ? -1 : 1); } this.movey += this.moveyspeed * frameMult; this.y = this.y + (this.movey * frameMult); if (this.y < 80) { this.y = 80; this.moveyspeed = Math.abs(this.moveyspeed); } else if (this.y > 240) { this.y = 240; this.moveyspeed = -Math.abs(this.moveyspeed); } } else if (this.movetype == "teleport") { switch (this.phase) { case 'takeoff': this.phaseProgress += (1/120) * frameMult; this.opacity = Math.min(this.phaseProgress / 0.15, 1); this.display = "inline-block"; if (this.phaseProgress >= 1) { this.phase = 'hidden'; this.phaseProgress = 0; } break; case 'hidden': this.phaseProgress += (1/60) * frameMult; this.opacity = Math.max(1 - this.phaseProgress / 0.3, 0); this.display = this.opacity > 0 ? "inline-block" : "none"; if (this.phaseProgress >= 1) { this.phase = 'takeoff'; this.phaseProgress = 0; this.x += this.movexspeed * 120 + Math.random() * this.width; this.opacity = 0; } break; } } else if (this.movetype == "wobble") { this.phaseProgress += (1/30) * frameMult; this.x += this.movexspeed * frameMult; const index = Math.trunc((this.phaseProgress % 2) * 180); this.angle = 3 * sineLUT[index]; this.nowwidth = this.width; this.nowheight = this.height; this.movey = 0; this.origin = "center center"; } else if (this.movetype == "wriggle") { switch (this.phase) { case 'takeoff': this.origin = "right bottom"; this.phaseProgress += (this.movexspeed / 60) * frameMult; this.nowwidth = this.width * (1 - this.phaseProgress * 0.4); this.x -= (this.width * this.movexspeed / 300) * frameMult; if (this.phaseProgress >= 1) { this.origin = "left bottom"; this.x += this.width * 0.4; this.phase = 'extend'; this.phaseProgress = 0; } break; case 'extend': this.phaseProgress += (this.movexspeed / 60) * frameMult; this.nowwidth = this.width * (0.6 + this.phaseProgress * 0.4); this.x += (this.width * this.movexspeed / 300) * frameMult; if (this.phaseProgress >= 1) { this.origin = "right bottom"; this.phase = 'takeoff'; this.phaseProgress = 0; } break; } } else if (this.movetype == "jump") { this.origin = "bottom center"; switch (this.phase) { case 'takeoff': this.phaseProgress += (1/15) * frameMult; this.nowheight = this.height * (1 - this.phaseProgress * 0.3); this.nowwidth = this.width * (1 + this.phaseProgress * 0.1); if (this.phaseProgress >= 1) { this.phase = 'jumping'; this.phaseProgress = 0; this.moveyspeed = 10; this.nowheight = this.height; this.nowwidth = this.width; } break; case 'jumping': this.phaseProgress += (1/30) * frameMult; this.movey += this.moveyspeed * frameMult; this.moveyspeed -= 0.5 * frameMult; this.x += (this.movexspeed * 5) * frameMult; if (this.movey >= this.height) { this.movey = this.height; this.moveyspeed = -this.moveyspeed * 0.3; } if (this.movey <= 0 && this.moveyspeed < 0) { this.movey = 0; this.moveyspeed = 0; this.phase = 'landing'; this.phaseProgress = 0; } break; case 'landing': this.phaseProgress += (1/20) * frameMult; this.nowheight = this.height * (0.7 + this.phaseProgress * 0.3); this.nowwidth = this.width * (1.1 - this.phaseProgress * 0.1); if (this.phaseProgress >= 1) { this.phase = 'takeoff'; this.phaseProgress = 0; this.nowheight = this.height; this.nowwidth = this.width; } break; } } else if (this.movetype == "crawl") { this.origin = "bottom center"; this.phaseProgress += (1/30) * frameMult; this.x += (this.movexspeed * 0.5) * frameMult; const index = Math.trunc((this.phaseProgress % 2) * 180); this.nowwidth = this.width * (1 + 0.1 * sineLUT[index]); this.nowheight = this.height * (1 - 0.1 * sineLUT[index]); this.angle = 0; } else if (this.movetype == "tremble") { this.phaseProgress += (1/60) * frameMult; this.x += (this.movexspeed * 0.3) * frameMult; this.nowwidth = this.width * (0.95 + Math.random() * 0.1); this.nowheight = this.height * (0.95 + Math.random() * 0.1); this.angle = 5 * (Math.random() * 2 - 1); } else if (this.movetype == "bounceroll") { this.origin = "center center"; switch (this.phase) { case 'takeoff': this.phaseProgress += (1/15) * frameMult; this.nowheight = this.height * (1 - this.phaseProgress * 0.3); this.nowwidth = this.width * (1 + this.phaseProgress * 0.1); if (this.phaseProgress >= 1) { this.phase = 'jumping'; this.phaseProgress = 0; this.moveyspeed = 7; this.nowheight = this.height; this.nowwidth = this.width; } break; case 'jumping': this.phaseProgress += (1/30) * frameMult; this.movey += this.moveyspeed * frameMult; this.moveyspeed -= 0.5 * frameMult; this.x += (this.movexspeed * 5) * frameMult; const radius = this.width / 2; const dtheta = ((this.movexspeed * 5 * frameMult) / radius) * (180 / Math.PI); this.angle += dtheta; this.angle %= 360; if (this.movey >= this.height / 2) { this.movey = this.height / 2; this.moveyspeed = -this.moveyspeed * 0.3; } if (this.movey <= 0 && this.moveyspeed < 0) { this.movey = 0; this.moveyspeed = 0; this.phase = 'landing'; this.phaseProgress = 0; } break; case 'landing': this.phaseProgress += (1/20) * frameMult; this.nowheight = this.height * (0.7 + this.phaseProgress * 0.3); this.nowwidth = this.width * (1.1 - this.phaseProgress * 0.1); if (this.phaseProgress >= 1) { this.phase = 'takeoff'; this.phaseProgress = 0; this.nowheight = this.height; this.nowwidth = this.width; } break; } }; if (this.x >= gridOffsetX - 32 - this.width / 2) { this.x = gridOffsetX - 32 - this.width / 2; this.hasReachedGate = true; if (defensePara.fortitude > 0) { defensePara.fortitude -= 2 * this.power; updatefortitude(); } else { defensePara.morale -= 2 * this.power; updatemorale(); } this.damageTimer = 0; } this.renderSprite(); if (this.hp <= 0) { this.element.remove(); enemies.splice(enemies.indexOf(this), 1); State.variables.battle.totalEnemiesKilled++; State.variables.battlebag.pickup("gold", this.power); if (this.drops) { this.drops.forEach(function(drop) { if (Math.random() < drop.probability) { if (drop.id.startsWith("skill")) { var config = {}; addLog('Dropped skill: ' + drop.id + " on the path"); dropitems.push(new Dropitem(drop.id, this.x, this.y, config)); } else { var config = { amount: drop.amount }; State.variables.battlebag.pickup(drop.id, drop.amount); addLog('Dropped ' + drop.id + " x" + drop.amount + " on the path and automatically picked up"); } } }, this); } else { console.error('No drops for enemy type:', this.id); } } if (currentWave >= State.variables.enemies.waves.length - 1) { if (enemies.length === 0 && spawnQueue.length === 0) { endBattle("victory"); } } }; function DefenderTroop() { var params = defensePara; this.attack = params.troopAttack; this.armor = params.troopArmor; this.hp = params.hp; this.maxHp = params.troopMaxHp; this.regen = params.troopRegen; this.speed = params.speed; this.width = params.width; this.height = params.height; this.combatDuration = params.combatDuration; this.x = gridOffsetX + 40; this.y = canvas.height / 2; this.isDeployed = true; this.isRetreating = false; this.currentSpeed = this.speed; this.pushTime = 0; this.lastAttackTime = 0; this.collidedEnemies = []; this.element = document.createElement('div'); this.element.className = 'defender-troop'; this.element.style.width = '102px'; this.element.style.height = '256px'; this.element.style.left = (this.x - this.width / 2) + 'px'; this.element.style.top = '15px'; this.hpElement = document.createElement('div'); this.hpElement.className = 'defender-troop-hp'; this.hpElement.style.left = (this.x - this.width / 2) + 'px'; this.hpElement.style.top = (this.y - this.height / 2 - 20) + 'px'; this.hpElement.textContent = `HP: ${Math.round(this.hp)}`; var container = document.querySelector('.game-container'); if (container) { container.appendChild(this.element); container.appendChild(this.hpElement); } else { console.error('Game container not found for DefenderTroop!'); } } DefenderTroop.prototype.update = function() { let dt = BattleTimer.dt; this.hp = Math.min(this.maxHp, this.hp + this.regen * dt); this.hpElement.textContent = `HP: ${Math.round(this.hp)}`; this.hpElement.style.left = (this.x - this.width / 2) + 'px'; this.hpElement.style.top = (this.y - this.height / 2 - 20) + 'px'; $("#defender-general-health").text(Math.round(this.hp) +"/"+ defensePara.troopMaxHp); if (this.hp <= 0) { this.destroy(); return; } this.collidedEnemies = []; for (var i = 0; i < enemies.length; i++) { var enemy = enemies[i]; var rx = enemy.x - enemy.width / 2; var ry = enemy.y - enemy.height / 2; if ( this.x - this.width / 2 < rx + enemy.width && this.x + this.width / 2 > rx && this.y - this.height / 2 < ry + enemy.height && this.y + this.height / 2 > ry ) { this.collidedEnemies.push(enemy); } } this.pushTime += dt; if (this.pushTime >= this.combatDuration && !this.isRetreating) { this.isRetreating = true; this.currentSpeed = this.speed; this.collidedEnemies = []; } if (this.isRetreating) { this.x += this.speed * dt; this.element.style.transform = "scaleX(-1)"; if (this.x >= gridOffsetX + 40) { this.destroy(); return; } } else if (this.collidedEnemies.length > 0) { var t = this.pushTime / 5; this.currentSpeed = this.speed * (1 - t); if (this.currentSpeed < 0) this.currentSpeed = 0; this.collidedEnemies.forEach(function(enemy) { enemy.x -= this.currentSpeed * dt; enemy.element.style.left = (enemy.x - enemy.width / 2) + 'px'; }, this); } else { this.currentSpeed = this.speed; if (this.x > 600) { this.x -= this.speed * dt; } } this.element.style.left = (this.x - this.width / 2) + 'px'; if (this.collidedEnemies.length > 0) { if (this.lastAttackTime === 0) { this.collidedEnemies.forEach(function(enemy) { enemy.hp -= this.attack * 2; enemy.element.style.filter = 'brightness(2)'; setTimeout(function() { if (enemy.element) enemy.element.style.filter = ''; }, 100); }, this); } this.lastAttackTime += dt; var attackInterval = 5 / this.speed; if (this.lastAttackTime >= attackInterval) { this.collidedEnemies.forEach(function(enemy) { enemy.hp -= this.attack; enemy.element.style.filter = 'brightness(2)'; setTimeout(function() { if (enemy.element) enemy.element.style.filter = ''; }, 100); }, this); this.lastAttackTime = 0; } this.collidedEnemies.forEach(function(enemy) { var damage = enemy.power * dt; var reduction = this.armor / (this.armor + 100); this.hp -= damage * (1 - reduction); }, this); } }; DefenderTroop.prototype.destroy = function() { defensePara.hp = this.hp; defensePara.isDeployed = false; if (this.element && this.element.parentNode) { this.element.remove(); } if (this.hpElement && this.hpElement.parentNode) { this.hpElement.remove(); } defenderTroop = null; }; var enemies = []; var defenderTroop = null; var currentWave = 0; var spawnQueue = []; function startWave(waveIndex) { spawnQueue = []; var wave = State.variables.enemies.waves[waveIndex]; wave.bosses.forEach(function(boss) { for (var i = 0; i < boss.count; i++) { spawnQueue.push(boss.id); const bossdata = generateSpecificEnemy(boss.id); State.variables.battle.attack.maxHealth = bossdata.hp; $("#attacker-attack-text").text(bossdata.power); updateStats(); } }); wave.minions.forEach(function(minion) { for (var i = 0; i < minion.count; i++) { spawnQueue.push(minion.id); } }); for (var i = spawnQueue.length - 1; i > 0; i--) { var j = Math.floor(Math.random() * (i + 1)); var temp = spawnQueue[i]; spawnQueue[i] = spawnQueue[j]; spawnQueue[j] = temp; } } var spawnTimer = 0; var waveTimer = 0; function spawnEnemy() { let dt = BattleTimer.dt; if (defensePara.morale <= 0) { endBattle("defeat"); return; } if (currentWave >= State.variables.enemies.waves.length) { if (enemies.length === 0 && spawnQueue.length === 0) { endBattle("victory"); } return; } if (spawnQueue.length === 0 && enemies.length === 0) { waveTimer += dt; if (waveTimer >= sv.enemies.waveInterval) { currentWave++; if (currentWave < State.variables.enemies.waves.length) { startWave(currentWave); addLog(`Wave ${currentWave + 1} approaches!`); } waveTimer = 0; } return; } spawnTimer += dt; if (spawnTimer >= State.variables.enemies.spawnInterval && spawnQueue.length > 0) { var enemyId = spawnQueue.shift(); enemies.push(new Enemy(enemyId)); spawnTimer = 0; } } startWave(0); $(document).on('change', 'input[name="radiobutton-battledefenseaimmode"]', function() { defensePara.aimMode = State.variables.battle.defense.aimMode; addLog('Aim mode changed to: ' + defensePara.aimMode); towers.forEach(function(tower) { tower.target = null; }); }); setup.SkillManager = { skills: { fireRateBoost: { name: "Fire Rate", description: "Increases all towers' fire rate by 50% for 10 seconds", duration: 10, cooldown: 30, effect: function() { addLog("Use skill book: Increases all towers' fire rate by 50% for 10 seconds"); const boost = 1.5; defensePara.towersBoost.allFireRate *= boost; towers.forEach(tower => tower.fireRate *= boost); BattleTimer.setTimeout(this.duration, () => { defensePara.towersBoost.allFireRate /= boost; towers.forEach(tower => tower.fireRate /= boost); }); } }, gateHeal: { name: "Repair", description: "Restores 10 HP to the gate", cooldown: 30, effect: function() { defensePara.fortitude = Math.min(defensePara.fortitude + 10, 1000); addLog("Use skill book: Restores 10 HP to the gate"); updatefortitude(); } }, freeze: { name: "Freeze", description: "Freezes all enemies for 5 seconds", duration: 5, cooldown: 30, effect: function() { addLog("Use skill book: Freezes all enemies for 5 seconds"); enemies.forEach(enemy => { enemy.originalSpeed = enemy.movexspeed; enemy.movexspeed = 0; }); BattleTimer.setTimeout(this.duration, () => { enemies.forEach(enemy => { if (enemy.originalSpeed !== undefined) { enemy.movexspeed = enemy.originalSpeed; delete enemy.originalSpeed; } }); }); } } }, cooldowns: {}, useSkill(skillId) { if (!(skillId in this.skills)) throw new Error(`Invalid skill: ${skillId}`); if (this.cooldowns[skillId]) return; if (State.variables.skillbag[skillId] <= 0) return; State.variables.skillbag[skillId]--; this.skills[skillId].effect(); if (this.skills[skillId].cooldown) { this.cooldowns[skillId] = this.skills[skillId].cooldown; this.renderSkillList(); this.updateCooldowns(); } }, updateCooldowns() { if (this._isUpdating) return; this._isUpdating = true; const ticker = () => { if (State.variables.battle.gameEnded) { this._isUpdating = false; return; } let hasActiveCd = false; let dt = window.BattleTimer.dt; for (let skillId in this.cooldowns) { if (this.cooldowns[skillId] > 0) { this.cooldowns[skillId] -= dt; hasActiveCd = true; if (this.cooldowns[skillId] <= 0) { this.cooldowns[skillId] = 0; } } } this.renderSkillList(); if (hasActiveCd) { requestAnimationFrame(ticker); } else { this._isUpdating = false; } }; requestAnimationFrame(ticker); }, renderSkillList() { const container = document.querySelector('.skillbook-container'); if (!container) return; container.innerHTML = Object.entries(State.variables.skillbag) .filter(([_, count]) => count > 0) .map(([skillId, count]) => { const skill = this.skills[skillId]; const remain = this.cooldowns[skillId] || 0; let buttonText = 'Use'; let isDisabled = false; if (remain > 0) { buttonText = `Cooling (${Math.ceil(remain)}s)`; isDisabled = true; } return ` <button class="skillbook-button" data-skill-id="${skillId}" ${isDisabled ? 'disabled' : ''}> <div class="skillbook-name">📜${skill.name}</div> <div class="skillbook-text">${buttonText}</div> <div class="skillbook-count"> x${count}</div> </button>`; }).join(''); }, init() { const container = document.querySelector('.skillbook-container'); if (!container) return; container.addEventListener('click', (e) => { const button = e.target.closest('.skillbook-button'); if (button && !button.disabled) { const skillId = button.dataset.skillId; this.useSkill(skillId); } }); this.renderSkillList(); } }; setup.SkillManager.init(); const StatsManager = { stats: [ { name: "Attack", id: "attack", format: (data) => data, source: () => defensePara.troopAttack }, { name: "Armor", id: "defense", format: (data) => data, source: () => defensePara.troopArmor }, { name: "Health", id: "health", format: (data) => `${data.hp}/${data.troopMaxHp}`, source: () => ({ hp: Math.max(0, Math.trunc(defensePara.hp)), troopMaxHp: defensePara.troopMaxHp }) }, { name: "Regen", id: "regen", format: (data) => `${data}/s`, source: () => defensePara.troopRegen }, { name: "Crit Chance", id: "crit", format: (data) => `${data}%`, source: () => defensePara.troopCrit }, { name: "Charge Time", id: "charge", format: (data) => `${(data / 100).toFixed(1)}s`, source: () => defensePara.charge } ], renderStats() { const container = document.getElementById('general-stats'); if (!container) return; container.innerHTML = this.stats .map(stat => { const data = stat.source(); const value = stat.format(data); return ` <div class="module-item-flex"> <span class="attribute-name">${stat.name}:</span> <span class="attribute-value" id="defender-general-${stat.id}">${value}</span> </div> `; }) .join(''); moraleRegen(); }, statstimer: null, init() { this.renderStats(); this.statstimer = setInterval(() => this.renderStats(), 1000); }, stop() { if (this.statstimer) { clearInterval(this.statstimer); this.statstimer = null; } } }; StatsManager.renderStats(); const TowersManager = { stats: [ { name: "Fire Rate Boost", id: "fireRate", format: (data) => `${(data * 100).toFixed(0)}%`, source: () => defensePara.towersBoost.allFireRate }, { name: "Damage Boost", id: "damage", format: (data) => `${(data * 100).toFixed(0)}%`, source: () => defensePara.towersBoost.allDamage }, { name: "Aim Mode", id: "aimMode", format: (data) => data || "Nearest", source: () => defensePara.aimMode }, { name: "Arrows Fired", id: "arrowCount", format: (data) => data.toLocaleString(), source: () => defensePara.arrowsFired || 0 }, { name: "Total DMG Dealt", id: "totalDmg", format: (data) => (data / 1000).toFixed(1) + "k", source: () => defensePara.totalDamageDealt || 0 }, { name: "Rescue Chance", id: "capturechance", format: (data) => `${(data * 100).toFixed(0)}%`, source: () => defensePara.captivesodds } ], renderStats() { const container = document.getElementById('towers-stats'); if (!container) return; container.innerHTML = this.stats .map(stat => { const data = stat.source(); const value = stat.format(data); return ` <div class="module-item-flex"> <span class="attribute-name">${stat.name}:</span> <span class="attribute-value" id="towers-general-${stat.id}">${value}</span> </div> `; }) .join(''); }, statstimer: null, init() { this.renderStats(); this.statstimer = setInterval(() => this.renderStats(), 1000); }, stop() { if (this.statstimer) { clearInterval(this.statstimer); this.statstimer = null; } } }; TowersManager.renderStats(); window.updateBattleStats = function() { TowersManager.renderStats(); StatsManager.renderStats(); }; const EnemiesManager = { stats: [ { name: "Enemy Count", id: "count", format: (data) => data, source: () => enemies.length }, { name: "Wave", id: "wave", format: (data) => `${data.current}/${data.total}`, source: () => ({ current: currentWave + 1, total: State.variables.enemies.waves.length }) }, { name: "Boss Count", id: "bossCount", format: (data) => data, source: () => enemies.filter(e => e.element.className.includes('boss')).length }, { name: "Total Enemies Killed", id: "killed", format: (data) => data, source: () => State.variables.battle.totalEnemiesKilled || 0 } ], renderStats() { const container = document.getElementById('enemies-stats'); if (!container) return; container.innerHTML = this.stats .map(stat => { const data = stat.source(); const value = stat.format(data); return ` <div class="enemies-stat module-item-flex"> <span class="enemies-stat-name">${stat.name}:</span> <span class="enemies-stat-value" id="enemies-general-${stat.id}">${value}</span> </div> `; }) .join(''); getBossHealth(); }, statstimer: null, init() { this.renderStats(); this.statstimer = setInterval(() => this.renderStats(), 1000); }, stop() { if (this.statstimer) { clearInterval(this.statstimer); this.statstimer = null; } } }; EnemiesManager.renderStats(); function endBattle(outcome) { State.variables.battle.gameEnded = true; addLog(outcome === "victory" ? "Victory achieved!" : "Defeat! The gate has fallen!"); addLog(outcome === "victory" ? "Resources have been automatically picked up and settled. The skill book requires you to click to pick it up!" : ""); State.variables.battle.defense = defensePara; const outcometext = outcome === "victory" ? "Battle_Victory" : "Event_Badend"; setup.Popup(`<<include ${outcometext}>>`, {mandatory: true, class: "popup-w80"}); resetChargingBars(); hideActionButtons(); TowersManager.stop(); StatsManager.stop(); EnemiesManager.stop(); document.getElementById("start-battle").disabled = true; $("#start-battle").text(outcome === "victory" ? "Victory!" : "Defeat!"); document.querySelectorAll(".action-button").forEach(btn => btn.disabled = true); document.querySelectorAll(".skillbook-button").forEach(btn => btn.disabled = true); }; function getEnemyWithMaxHP() { if (enemies.length === 0) return null; let maxEnemy = enemies[0]; for (let i = 1; i < enemies.length; i++) { if (enemies[i].hp > maxEnemy.hp) { maxEnemy = enemies[i]; } } return maxEnemy; }; function gameLoop(currentTime) { if (State.variables.battle.gameEnded) return; if (!BattleTimer.lastTime) { BattleTimer.lastTime = currentTime; } let rawDt = (currentTime - BattleTimer.lastTime) / 1000; BattleTimer.lastTime = currentTime; if (rawDt > 0.1) rawDt = 0.1; if (BattleTimer.isPaused) { requestAnimationFrame(gameLoop); return; } BattleTimer.dt = rawDt * BattleTimer.timeScale; let dt = BattleTimer.dt; ctx.clearRect(0, 0, canvas.width, canvas.height); spawnEnemy(); enemies.forEach(function(enemy) { enemy.update(); }); towers.forEach(function(tower) { tower.fire(); }); arrowPool.update(dt); arrowPool.draw(); if (defenderTroop) { defenderTroop.update(); } dropitems.forEach(function(item) { }); BattleTimer.updateTimers(dt); if (defensePara.isCharging) { if (!chargeCurrentTime) chargeCurrentTime = 0; chargeCurrentTime += dt; let durationSec = defensePara.charge / 100; let pct = (chargeCurrentTime / durationSec) * 100; if (pct >= 100) { pct = 100; defensePara.isCharging = false; chargeCurrentTime = 0; showActionButtons(); updateState(); } $("#defender-charging-bar").css({ width: pct + "%", transition: "none" }); } requestAnimationFrame(gameLoop); } window.BattleTimer = { lastTime: 0, dt: 0, timeScale: 1, options:[0.25, 0.5, 1, 2, 3, 4, 5, 10], currentIndex: 2, isPaused: false, timers:[], setTimeout: function(durationSec, callback) { this.timers.push({ remaining: durationSec, cb: callback }); }, updateTimers: function(dt) { for (let i = this.timers.length - 1; i >= 0; i--) { this.timers[i].remaining -= dt; if (this.timers[i].remaining <= 0) { try { this.timers[i].cb(); } catch(e) { console.error(e); } this.timers.splice(i, 1); } } } }; window.updateSpeedUI = function() { let currentSpeed = BattleTimer.options[BattleTimer.currentIndex]; if (BattleTimer.isPaused) { $('#btn-speed-toggle').html('⏸️ Paused').addClass('paused'); } else { let speedText = Number.isInteger(currentSpeed) ? currentSpeed + '.0' : currentSpeed; $('#btn-speed-toggle').html(`▶ ${speedText}x`).removeClass('paused'); } $('#btn-speed-down').prop('disabled', BattleTimer.currentIndex === 0); $('#btn-speed-up').prop('disabled', BattleTimer.currentIndex === BattleTimer.options.length - 1); BattleTimer.timeScale = currentSpeed; }; window.startBattle = function (){ if (sv.battle.gameStarted) return; sv.battle.gameStarted = true; addLog("Battle commenced!"); BattleTimer.lastTime = performance.now(); BattleTimer.timeScale = 1; BattleTimer.isPaused = false; BattleTimer.timers =[]; chargeCurrentTime = 0; arrowPool.init(); initTowers(); startCharging(defensePara); EnemiesManager.init(); TowersManager.init(); StatsManager.init(); $('#battle-speed-panel').show(); requestAnimationFrame(gameLoop); }; window.recalculateBattleStats = function(actionType) { if (typeof StatsManager !== 'undefined') StatsManager.renderStats(); if (typeof TowersManager !== 'undefined') TowersManager.renderStats(); defensePara.fortitude = Math.min(defensePara.fortitude, defensePara.maxfortitude); updatefortitude(); const newCapacity = defensePara.captivesCount; const currentSlots = $("#defender-captives .sanctuary-slot").length; if (newCapacity !== currentSlots) { $("#defender-captives").empty(); for (let i = 0; i < newCapacity; i++) { const isFilled = defensePara.captives[i] ? "filled" : ""; $("#defender-captives").append(`<div class="sanctuary-slot ${isFilled}"></div>`); } if (newCapacity > currentSlots) { Msg.notify(`Dungeon Expanded! Capacity: ${newCapacity}`); } } $('.attribute-value, #defender-fortification-text').addClass('stat-flash'); setTimeout(() => { $('.attribute-value, #defender-fortification-text').removeClass('stat-flash'); }, 500); }; $(document).off('click', '#btn-speed-down').on('click', '#btn-speed-down', function(e) { e.preventDefault(); if (State.variables.battle.gameEnded) return; if (BattleTimer.currentIndex > 0) { BattleTimer.currentIndex--; updateSpeedUI(); let s = BattleTimer.options[BattleTimer.currentIndex]; addLog(`Battle speed decreased to ${s}x.`); } }); $(document).off('click', '#btn-speed-up').on('click', '#btn-speed-up', function(e) { e.preventDefault(); if (State.variables.battle.gameEnded) return; if (BattleTimer.currentIndex < BattleTimer.options.length - 1) { BattleTimer.currentIndex++; updateSpeedUI(); let s = BattleTimer.options[BattleTimer.currentIndex]; addLog(`Battle speed increased to ${s}x.`); } }); $(document).off('click', '#btn-speed-toggle').on('click', '#btn-speed-toggle', function(e) { e.preventDefault(); if (State.variables.battle.gameEnded) return; BattleTimer.isPaused = !BattleTimer.isPaused; if (!BattleTimer.isPaused) { BattleTimer.lastTime = performance.now(); addLog("Battle resumed."); } else { addLog("Battle paused."); } updateSpeedUI(); }); $(document).off('keydown.battleSpeed').on('keydown.battleSpeed', function(e) { if (!sv.battle.gameStarted || sv.battle.gameEnded) return; if (['INPUT', 'TEXTAREA'].includes(e.target.tagName)) return; switch(e.key) { case ' ': e.preventDefault(); $('#btn-speed-toggle').click(); break; case 'ArrowLeft': e.preventDefault(); $('#btn-speed-down').click(); break; case 'ArrowRight': e.preventDefault(); $('#btn-speed-up').click(); break; } }); EventBus.on("battle_end", () => { $(document).off('keydown.battleSpeed'); }); $(document).off("click", ".battle-flyout-trigger").on("click", ".battle-flyout-trigger", function(e) { e.preventDefault(); let $wrapper = $(this).closest('.battle-flyout-wrapper'); let $panel = $wrapper.find('.battle-flyout-panel'); let $toggleIcon = $(this).find('.battle-flyout-toggle'); $wrapper.toggleClass('is-open'); if ($wrapper.hasClass('is-open')) { $panel.stop(true, true).fadeIn(200); $toggleIcon.css('transform', 'rotate(180deg)'); } else { $panel.stop(true, true).fadeOut(200); $toggleIcon.css('transform', 'rotate(0deg)'); } }); <</script>> <</done>>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="battle Gcontainer" id="passagecontent"> <<includeplus "Battle">> </div> <<include "UI_RightBar">> </div> </div>
<div class="victory-box"> <div class="victory-header">Victory</div> <div class="reward-panel"> <h2 class="item-illuminated-text">Spoils of War</h2> <div class="reward-grid"> <div class="reward-item"> <h3>Slaughter’s Glory</h3> <p>Demons Slain: <<= $battle.totalEnemiesKilled>></p> <<set _GainedXP = Math.trunc($battle.totalEnemiesKilled * $times.total / 10)>> <<set _generalsid = [$battle.defense.generals.Commander.id, $battle.defense.generals.Warden.id, $battle.defense.generals.Bard.id, $battle.defense.generals.Guardian.id, $battle.defense.generals.Ranger.id, $battle.defense.generals.Strategist.id]>> <<run $chars.templeList.filter(monsterId => _generalsid.includes(monsterId)).forEach(monsterId => $chars.dataMap.get(monsterId).gainxp(_GainedXP / 6));>> <p>Experience Gained: <<= _GainedXP>></p> <p style="font-style: italic;">The EXP will be divided equally among the characters who participate in the battle!</p> <p>Piety: +5</p><<set $domain.piety = Math.clamp($domain.piety + 5 , 0, 200) >> </div> <div class="reward-item"> <<set _humanpd = random(10, $battle.totalEnemiesKilled)>> <h3>Barony's Renown</h3> <p>Refugees’ Allegiance: <<= _humanpd>> Humans</p> <p style="font-style: italic;">Word of your victory spreads, drawing more refugees to seek your protection!</p> <div class="refugee-actions"> <<button "Accept">> <<replace ".refugee-actions">>Accepted!<<run PopSystem.autoSettle("human", _humanpd)>><<redo tag 'topbar'>><</replace>> <</button>> <<button "Reject">> <<replace ".refugee-actions">>Rejected!<<set _humanpd = 0>><</replace>> <</button>> </div> </div> <div class="reward-item"> <h3 class="item-illuminated-text">Plunder</h3> <div class="loot-list"> <<inv $battlebag>> </div> </div> </div> </div> <div class="reward-panel"> <h2 class="item-illuminated-text">Rescued Monsters</h2> <<do tag "prisoner">> <div class="prisoner-list"> <<for _i range $battle.defense.captives.length>> <div class="prisoner-card"> <h3>???</h3> <p>Race: ???</p> <div class="prisoner-actions"> <<button "Examine">> <<nobr>> <<run $battle.defense.captives.splice(_i, 1);>> <<run setup.Popup('<<include "Battle_ExamineCaptive">>', { buttons: [ { text: "Ignore", cancel: () => setup.Popup.close() } ] })>> <<redo 'prisoner'>> <</nobr>> <</button>> </div> </div> <</for>> </div> <</do>> </div> <div style="text-align:center; margin-top:15px;"> <<silent>> <<removeclass ".UItopbar" "hidden">> <<removeclass ".RightBar" "hidden">> <<run UIBar.stow().show()>> <<= $storage.merge($battlebag)>> <<SetFlag 'battleday' false>> <<set $battle.demonattackdeadline = 7 >> <</silent>> <<button "Return in Glory">> <<WonRun>> <<run setup.Popup.closeAll();>> <</button>> </div> </div>
<<widget "WonRun">> <<run Evm.hook("battle_won", $battleInfo)>> <<if !Flag('miraelflag')>><<run Evm.flash('Event_Mirael_prisoner')>><</if>> <<SetFlag 'battlevictory' 1>> <<run Dialog.close();>> <</widget>> <<widget BattleWaves>> <<if _args[0]>> <<set _weekindex = _args[0] >> <<else>> <<set _weekindex = Math.max(Math.trunc($times.total / 7), 1)>> <</if>> <<if _weekindex == 1>> <<set $enemies = setup.weekEnemieWaves[1] >> <<elseif _weekindex == 2>> <<set $enemies = setup.weekEnemieWaves[2] >> <<elseif _weekindex >= 32>> <<set _countMultiplier = Math.max(Math.trunc((_weekindex - 32) / 4), 2)>> <<set $enemies = generateWeekEnemyWaves(19, 4, _countMultiplier) >> <<elseif _weekindex >= 20>> <<set _countMultiplier = Math.max(Math.trunc((_weekindex - 20) / 4), 2)>> <<set $enemies = generateWeekEnemyWaves(19, 3, _countMultiplier) >> <<else>> <<set $enemies = generateWeekEnemyWaves(_weekindex) >> <</if>> <</widget>> <<widget GeneralFilter>> <<set _newquest = new Quest({})>> <<set _newquest.reqlevel= 10>> <<set _newquest.hasunique= true>> <<set _filtergather = filterMonsterByCondition($chars.templeList, _newquest).ids>> <<set $chars.selected = null>> <div class="itemfilter-title">General Filter</div> <div class="itemfilter"> <div class="itemfilter-list"> <<MapList _filtergather>> </div> <div class="itemfilter-info"> <<do tag "selected">> <<if $chars.selected>> <<Infocard $chars.selected>> <</if>> <</do>> <div class="itemfilter-requirement-card"> <h3>Requirements</h3> <div class="itemfilter-requirement-content"> Level >= 10; </div> </div> <div class="itemfilter-act"> <<button "Join">> <<if !$chars.selected>> <<run Msg.flash(L.selecttarget)>> <<elseif $chars.selected.worktags>> <<run Msg.flash(L.Alreadywork)>> <<elseif $battle.defense.generals[_args[0]].id != "">> <<set _genpost = $battle.defense.generals[_args[0]] >> <<set _postchar = getMonsterByID($battle.defense.generals[_args[0]].id)>> <<set _postchar.worktags = "">> <<set _genpost['id'] = "" >> <<set _genpost['id'] = $chars.selected.id >> <<set $chars.selected.worktags = "🛡️" >> <<set $chars.selected = null>> <<redo _args[0]>><<run updateBattleStats()>> <<run Dialog.close()>> <<else>> <<set _genpost = $battle.defense.generals[_args[0]] >> <<set _genpost['id'] = $chars.selected.id >> <<set $chars.selected.worktags = "🛡️" >> <<set $chars.selected = null>> <<redo _args[0]>><<run updateBattleStats()>> <<run Dialog.close()>> <</if>> <</button>> <<button "Empty">> <<set _genpost = $battle.defense.generals[_args[0]] >> <<if _genpost.id != "">> <<set _postchar = getMonsterByID($battle.defense.generals[_args[0]].id)>> <<set _postchar.worktags = "">> <<set _genpost['id'] = "" >> <<redo _args[0]>><<run updateBattleStats()>> <<run Dialog.close()>> <</if>> <</button>> <<button "Close">> <<run Dialog.close()>> <</button>> </div> </div> </div> <</widget>>
<div class="bedroom-panel"> <div class="nav-list"> <<link '<div class="nav-tab" id="bedroomsleep"><<= L.sleep>></div>'>> <<if Flag('battleday')>> <<run Msg.flash(L.notimesleep);>> <<elseif setup.validateDayTransition() === false>> <<else>> <<run EffectEngine.playSleep(() => { Engine.play("Bedroom_UI"); $gamestatus.autoSave(); $.wiki(`<<Sleep>>`); })>> <</if>> <<if Flag('tutorial') == 6>><<SetFlag 'tutorial' 7>><</if>> <</link>> <<if Flag('eninfuser') >= 2>> <<link "<div class='nav-tab' id='Girls'><<= '🤰Girls'>></div>">><<ToggleTags "nav-tab" 'Girls'>><<replace "#bedroom-panel">><<include "Girl">><</replace>><</link>> <</if>> <<if Flag('tutorial') >= 6>> <<link "<div class='nav-tab' id='codex'><<= '✍Codex'>></div>">><<ToggleTags "nav-tab" 'codex'>><<replace "#bedroom-panel">><<include "Codex">><</replace>><</link>> <<link "<div class='nav-tab' id='Bestiary'><<= '📒Bestiary'>></div>">><<ToggleTags "nav-tab" 'Bestiary'>><<goto "Bestiary_UI">><</link>> <<link "<div class='nav-tab' id='Jour'><<= '⁉️Quest'>></div>">><<ToggleTags "nav-tab" 'Jour'>><<replace "#bedroom-panel">><<include "Quest">><</replace>><</link>> <<link "<div class='nav-tab' id='Report'><<= '📊Report'>></div>">><<ToggleTags "nav-tab" 'Report'>><<replace "#bedroom-panel">><<include "DomainReport">><</replace>><</link>> <</if>> <<link "<div class='nav-tab'><<= setup.langbank.savegame[$lang]>></div>" "Bedroom_UI">><<run UI.saves()>><</link>> </div> <div class="content-panel" id="bedroom-panel"> <center><h1>Calendar</h1> <h2><<= setup.TimeSystem.getCurrentInfo()>> <br>Day $times.total</h2></center> </div>
<<SimpleUI "Bedroom_MorningNews" "fullpop">>
<div class="news-container"> <<if !setup.isCurrentView("Meeting")>> <<set _newsimage = [ 'event/memory/hole.webp', 'event/memory/run.webp', 'event/memory/lost.webp', 'event/memory/entry.webp' ].random(); >> <<run AudioBus.playPlaylist('theme_morning');>> <div class="news-banner"> <img @src="setup.ImagePath+_newsimage" class="news-banner-image"> </div> <</if>> <div class="news-body"> <div class="news-panel"> <h3 class="news-title"><<= setup.langbank.worldinfo[$lang]>></h3> <div class="news-item"> <h4><span class="urgent-mark" style="color:#6b0504">⚔</span><<= L.survivors>>: <<= "4??M"+" (War chaos, no precise stats)">> </h4> </div> <div class="news-item"> <h4><span class="urgent-mark" style="color:#6b0504">⚔</span><<= L.fiefdoms>>: <<= "2???"+" (War chaos, no precise stats)">> </h4> </div> <<if Flag('wartaxs')>> <div class="news-item"> <h4><<if $battle.demonattackdeadline == 0>>The demons are massing and will attack the Mountain Pass today!! <<else>>The Dark Legion are expected to attack in <<= $battle.demonattackdeadline>> days! <</if>></h4> </div> <div class="news-item"> <h4><<= L.remaining>> <<= $domain.wartaxdeadline>> <<= L.day>>,<<= L.taxsubmit>> <<= $domain.wartax[0]>>g <<= L.taxes>></h4> </div><</if>> </div> <div class="news-panel"> <h3 class="news-title"><<= L.territory>></h3> <div class="news-item"> <<set _jobCache = JobSystem.getPreview()>> <h4><<= "Total Yield">>: (<<= "Workers">>:<<= $job?.summary?.workers || 0>>)<br> <<= L.gold>>:<<= _jobCache.totalIncome?.gold || 0>>, <<= L.food>>:<<= _jobCache.totalIncome?.food || 0>>, <<= L.wood>>:<<= _jobCache.totalIncome?.wood || 0>>, <<= L.stone>>:<<= _jobCache.totalIncome?.stone || 0>>, <<= L.ore>>:<<= _jobCache.totalIncome?.ore || 0>>, <<= "Crystal">>:<<= _jobCache.totalIncome?.crystal || 0>> </h4> </div> <div class="news-item"> <h4><<= L.currentgold>>: <<= $storage.count('gold')>>g</h4> </div> </div> </div> <<if !setup.isCurrentView("Meeting")>> <div class="news-seal"> <<link [img[setup.ImagePath + 'ui/icon/waxsealred.png']]>> <<if $gamestatus.isLoad == true>><<set $gamestatus.isLoad = false >><</if>> <<NextDayEvent>> <<run EventBus.emit('time:morning')>> <<goto 'MainHall_UI'>><<run Dialog.close()>> <</link>> </div> <</if>> </div>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<includeplus "Bedroom">> </div> <<include "UI_RightBar">> </div> </div> <<done>> <<if Flag('tutorial') == 6>> <<append "#bedroomsleep">> <<set _tutorl = [ { el: '#bedroomsleep', text: L.sleepdesc} ] >> <<run UIBar.show().stow();>> <<TutorList _tutorl>> <</append>> <</if>> <</done>>
<div class="bestiary-panel"> <div class="tree-originbar"> <div class="originbar-header"> <div class="originbar-title">Bestiary</div> </div> <ul class="tree-list" id="tree-list"> </ul> <div class="progress-stats"> <div class="stat-row"> <span class="stat-label">Discovered:</span> <span class="stat-value" id="discovered-count">0</span> </div> <div class="stat-row"> <span class="stat-label">Total Species:</span> <span class="stat-value" id="total-count">0</span> </div> <div class="progress-bar mt-2"> <div class="progress-fill" id="discovered-progress" style="width: 0%"></div> </div> </div> </div> <div class="bestiary-content"> <div class="content-header"> <div class="info-core"> <div class="content-title title" id="content-title">The Bestiary</div> <div class="content-subtitle meta-tags" id="content-subtitle"></div> </div> <div class="info-desc"> <div id="head-desc">A comprehensive log of all encountered lifeforms. Study their traits, unlock their secrets, and map their lineage.</div> </div> </div> <div class="content-body" id="content-body"> <div class="species-illustration"> <div class="species-image silhouette"> <div class="species-image-placeholder">Select a monster</div> </div> <div class="bestiary-speech-bubble"> <div class="speech-text" id="speech-text">"Knowledge is power... and pleasure."</div> </div> </div> <div class="species-stats"> <div class="section-panel"> <div class="section-title">System Guide</div> <div class="text-fs085">Select a category from the left to begin.</div> <div class="module-ul mt-4"> <li>Defeat monsters or reach intimacy levels to unlock data.</li> <li>Each monster has multiple lore entries and mechanics.</li> </div> </div> </div> </div> </div> </div> <<done>> <<script>> (function() { const UI_CONFIG = setup.UI_CONFIG; const Utils = { html: { progressBar: (val, max, colorClass) => { const pct = Math.min(100, Math.max(0, (val / max) * 100)); return `<div class="stat-progress"><div class="progress-bar"><div class="progress-fill ${colorClass}" style="width: ${pct}%"></div></div></div>`; }, tag: (text, cls = 'tag-trait') => `<span class="tag ${cls}">${text}</span>`, statCard: (label, val, barHtml = '') => ` <div class="stat-card"> <div class="stat-label">${label}</div> <div class="stat-value">${val}</div> ${barHtml} </div>`, statBar: (label, val, barHtml = '') => ` <div class="stat-bar"> <div class="stat-name">${label}</div> <div class="stat-value">${val}</div> ${barHtml} </div>`, section: (title, content, subtext = '') => ` <div class="section-panel"> <div class="section-title">${title}</div> ${subtext ? `<div class="section-desc">${subtext}</div>` : ''} ${content} </div>` } }; class speciesbestiary { constructor() { this.state = State.variables.bestiary; this.dom = { tree: document.getElementById('tree-list'), title: document.getElementById('content-title'), subtitle: document.getElementById('content-subtitle'), headdesc: document.getElementById('head-desc'), body: document.getElementById('content-body'), speech: document.getElementById('speech-text'), stats: { disc: document.getElementById('discovered-count'), total: document.getElementById('total-count'), bar: document.getElementById('discovered-progress') } }; EventBus.on("data:updated", (payload) => { if (this.state.ui.lastSelectedId && this.state.ui.lastSelectedId.id === payload.speciesId) { this.updateContent(this.state.ui.lastSelectedId, 3); } }); this.init(); } init() { this.renderTree(); this.updateGlobalStats(); if (this.state.ui.lastSelectedId) { const { data, level } = this.state.ui.lastSelectedId; if(level === 3) { const freshData = this.getTreeDataNode(data.id, level); this.updateContent(freshData, 3); } else { this.updateContent(data, level); } const activeNode = this.dom.tree.querySelector(`.tree-item[data-id="${data.id}"] > .tree-node`); if(activeNode) activeNode.classList.add('active'); } } getTreeDataNode(id, level) { if (level === 3) return { id: id, type: 'species' }; if (level === 2) return { id: id, type: 'lineage' }; if (level === 1) return { id: id, type: 'origin' }; } getTreeData() { const structure = {}; const originIds = Object.keys(setup.originDB); originIds.forEach(originId => { const originVM = setup.speciesAssembler.assembleOrigin(originId); if (!originVM) return; structure[originId] = { id: originVM.id, name: originVM.name, type: 'origin', unlocked: originVM.isUnlocked, inProgress: originVM.inProgress, lineages: {} }; if (originVM.lineages && originVM.lineages.length > 0) { originVM.lineages.forEach(lineageId => { const famVM = setup.speciesAssembler.assembleLineage(lineageId); if (!famVM) return; structure[originId].lineages[lineageId] = { id: famVM.id, name: famVM.name, type: 'lineage', origin: originId, unlocked: famVM.isDiscovered, inProgress: famVM.inProgress, speciess: famVM.members }; }); } }); return structure; }; renderTree = function() { this.dom.tree.innerHTML = ''; const data = this.getTreeData(); Object.values(data).forEach(origin => { const originItem = this.createNode(origin, 1); this.dom.tree.appendChild(originItem); if (this.state.ui.expandedNodes[`origin:${origin.id}`]) { const famList = originItem.querySelector('.tree-children'); if (origin.lineages) { Object.values(origin.lineages).forEach(fam => { const famItem = this.createNode(fam, 2); famList.appendChild(famItem); if (this.state.ui.expandedNodes[`lineage:${fam.id}`]) { const monList = famItem.querySelector('.tree-children'); if (fam.speciess && Array.isArray(fam.speciess)) { fam.speciess.forEach(mon => { monList.appendChild(this.createNode(mon, 3)); }); } } }); } } }); }; createNode = function(data, level) { const li = document.createElement('li'); li.className = `tree-item level-${level}`; li.dataset.id = data.id; let nameColor = ""; let isLocked = false, isUnknown = false; if (level === 1 && !data.unlocked) isLocked = true; if (level === 3) { if (!data.isKnown) isUnknown = true; nameColor = setup.rarityData[data.rank].UIColor; } let icon = setup.UI_CONFIG.icons.origin; if (data.inProgress) icon = setup.UI_CONFIG.icons.development; else if (isLocked) icon = setup.UI_CONFIG.icons.locked; else if (level === 2) icon = setup.UI_CONFIG.icons.lineage; else if (level === 3) icon = isUnknown ? setup.UI_CONFIG.icons.locked : setup.UI_CONFIG.icons.species; let displayName = data.name; if (isUnknown) displayName = "???"; const expandKey = `${level === 1 ? 'origin' : 'lineage'}:${data.id}`; const isExpanded = this.state.ui.expandedNodes[expandKey]; const hasChildren = level < 3; const rankHtml = (level === 3 && !isUnknown && data.rank) ? `<div class="node-status"><span class="node-rank">${setup.renderRarity(data.rank)}</span></div>` : ''; const nodeDiv = document.createElement('div'); nodeDiv.className = `tree-node ${data.inProgress ? 'in-progress' : ''} ${isLocked || isUnknown ? 'locked' : ''}`; if (this.state.ui.lastSelectedId && this.state.ui.lastSelectedId.id === data.id) { nodeDiv.classList.add('active'); } nodeDiv.innerHTML = ` <div class="node-icon" style="color:${nameColor}">${icon}</div> <div class="node-name ${isUnknown ? 'unknown' : ''}" style="color:${nameColor}">${displayName}</div> ${hasChildren ? `<div class="node-expander ${isExpanded ? 'expanded' : ''}"><i class="fas fa-chevron-right"></i></div>` : ''} `; if (!data.inProgress && !(level === 1 && isLocked)) { nodeDiv.addEventListener('click', (e) => { e.stopPropagation(); this.handleNodeClick(data, level, li, isExpanded); }); } li.appendChild(nodeDiv); if (hasChildren) { const ul = document.createElement('ul'); ul.className = `tree-children ${isExpanded ? 'expanded' : ''}`; li.appendChild(ul); } return li; }; handleNodeClick(data, level, liElement, currentlyExpanded) { const selection = { id: data.id, type: level === 1 ? 'origin' : (level === 2 ? 'lineage' : 'species'), level, data }; this.state.ui.lastSelectedId = selection; document.querySelectorAll('.tree-node.active').forEach(el => el.classList.remove('active')); liElement.querySelector('.tree-node').classList.add('active'); if (level < 3) { const key = `${level === 1 ? 'origin' : 'lineage'}:${data.id}`; this.state.ui.expandedNodes[key] = !currentlyExpanded; this.renderTree(); const newActive = this.dom.tree.querySelector(`.tree-item[data-id="${data.id}"] > .tree-node`); if(newActive) newActive.classList.add('active'); } this.updateContent(data, level); } updateContent(data, level) { if (level === 1) { const viewModel = setup.speciesAssembler.assembleOrigin(data.id); if (viewModel) { this.renderOriginView(viewModel); } } else if (level === 2) { const viewModel = setup.speciesAssembler.assembleLineage(data.id); if (viewModel) { this.renderLineageView(viewModel); } } else if (level === 3) { bestiaryManager.scanForUnlocks(data.id); const viewModel = setup.speciesAssembler.assemble(data.id); if (viewModel) { const mStandard = this.createStandardModel(viewModel); this.renderspeciesView(viewModel, mStandard); } } } createStandardModel(viewModel) { return setup.createStandardMonster(viewModel.lineage.id, viewModel.id, viewModel.baseInfo.rank); } updateHeader(title, subtitle, headdesc = "", headprogress = "") { this.dom.title.textContent = title; this.dom.subtitle.textContent = subtitle; this.dom.headdesc.textContent = headdesc; } renderOriginView(viewModel) { const isDev = viewModel.inProgress; this.updateHeader( isDev ? `[WIP] ${viewModel.name}` : viewModel.name, isDev ? "Under Development" : `${viewModel.stats.discovered}/${viewModel.stats.total} Species Discovered`, `"This domain contains ${viewModel.stats.total} known species."` ); let htmlContent = ` <div class="species-illustration"> <div class="species-image"> <div class="species-image-placeholder">${viewModel.name}</div> </div> </div> <div class="species-stats"> ${Utils.html.section(viewModel.name, `<div class="text-fs085">${viewModel.desc}</div>`)} ${ ''} ${Utils.html.section('Discovery Progress', ` <div class="progress-bar"> <div class="progress-fill" style="width: ${viewModel.stats.progress}%"></div> </div> <div class="flex-row-sb1 mt-1 text-fs075"> <span>${viewModel.stats.discovered}/${viewModel.stats.total} Unlocked</span> <span>${viewModel.stats.captured} Captured</span> </div> `)} ${isDev ? `<div class="locked-info mt-4"><div class="locked-text">Coming Soon</div></div>` : ''} </div> `; this.dom.body.innerHTML = htmlContent; } renderLineageView(viewModel) { const isDev = viewModel.inProgress; this.updateHeader( isDev ? `[WIP] ${viewModel.name}` : `${viewModel.name} Lineage`, `${viewModel.stats.discovered}/${viewModel.stats.total} Members Discovered` ); const traitsHtml = viewModel.traits.map(t => Utils.html.tag(t)).join(''); const membersHtml = viewModel.members.map(m => { return ` <div class="ability-item"> <div class="ability-name">${m.name}</div> <div class="ability-desc"> ${m.isKnown ? `Rank: ${m.rank} • Status: ${m.status}` : 'Undiscovered'} </div> </div>`; }).join(''); this.dom.body.innerHTML = ` <div class="species-illustration"> <div class="species-image"><div class="species-image-placeholder"><i class="fas ${viewModel.icon} fa-3x"></i></div></div> </div> <div class="species-stats"> ${Utils.html.section('Lineage Traits(WIP)', `<div class="tags-container">${traitsHtml}</div>`,`Research Level: ${viewModel.researchLv}` )} ${Utils.html.section('Known Members', `<div class="ability-list">${membersHtml}</div>`)} ${isDev ? '<div class="locked-info mt-2">Under Construction</div>' : ''} </div> `; } renderspeciesView(viewModel, mStandard) { const isUnknown = viewModel.status === "unknown"; this.updateHeader( isUnknown ? "???" : viewModel.baseInfo.name, isUnknown ? "Unencountered Beast" : `Rarity: ${setup.rarityData[viewModel.baseInfo.rank].fullName } | ${viewModel.baseInfo.title}`, isUnknown ? "..." : viewModel.desc, isUnknown ? "..." : `${viewModel.researchProgress}%` ); if (isUnknown) { this.dom.body.innerHTML = ` <div class="species-illustration"><div class="species-image silhouette"><div class="species-image-placeholder">Untamed Shadow</div></div></div> <div class="species-stats"> <div class="section-panel"> <div class="locked-info"> <div class="locked-text">An unencountered form. Seek her out in the wild to reveal her entry.</div> </div> </div> </div>`; return; } const mkTag = (txt, cls) => `<span class="tag ${cls}">${txt}</span>`; const mkGenderTag = (gender) => { if (gender === 'male') return `<span class="tag-icon tag-gender-male" title="Male Nature"><i class="fas fa-mars"></i></span>`; if (gender === 'female') return `<span class="tag-gender-female" title="Female Nature"><i class="fas fa-venus"></i></span>`; return `<span class="tag-icon tag-gender-both" title="Shared Nature"><i class="fas fa-venus-mars"></i></span>`; }; const commonGenderTag = (gender) => { if (gender === 'boy') return `<span class="tag-icon tag-gender-male" title="Common Gender : Male"><i class="fas fa-mars"></i></span>`; if (gender === 'girl') return `<span class="tag-gender-female" title="Common Gender : Female"><i class="fas fa-venus"></i></span>`; return `<span class="tag-icon tag-gender-both" title="Common Gender : Male & Female"><i class="fas fa-venus-mars"></i></span>`; }; const mkSourceTag = (source) => { if (source === 'lineage') { return `<span class="tag-icon tag-source-lineage" title="Bloodline Shared (Applies to all kin)">🩸</span>`; } return `<span class="tag-icon tag-source-variant" title="Species Unique (Exclusive to this form)">💧</span>`; }; const mkStatusTag = (unlocked) => { return unlocked ? `<span class="tag-icon tag-status-unlocked" title="Unlocked Entry">${UI_CONFIG.icons.check}</span>` : `<span class="tag-icon tag-status-locked" title="Locked Entry">${UI_CONFIG.icons.locked}</span>`; }; const renderSwatches = (colors, label) => { if (!colors || colors.length === 0) return ''; const dots = colors.map(c => `<span class="swatch-dot" style="background:${c};" title="Naturally inherited color."></span>`).join(''); return `<div class="swatch-row"><span class="swatch-label">${label}:</span> <div class="swatch-dots">${dots}</div></div>`; }; const skinSwatches = renderSwatches(viewModel.skinColor, '<i class="fa-solid fa-palette"></i> Skin'); const furSwatches = renderSwatches(viewModel.furColor, '<i class="fa-solid fa-palette"></i> Hair'); const traitsHtml = viewModel.traits.map(t => mkTag(t, 'tag-trait')).join(''); const geneListHtml = Object.entries(viewModel.uniqueGenes) .map(([k, v]) => `<span class="tag tag-gene"><i class="fas fa-microscope"></i> ${k.toUpperCase()}: ${v}</span>`) .join(''); const itemHtml = ` <div class="tags-container"> ${viewModel.drops.map(d => { return Utils.html.tag(`${d.name} (${d.rate*100}%)`, UI_CONFIG.combatTags.drop); }).join('')} </div> `; const itemHtmlLite = ` ${viewModel.drops.map(d => { return `${d.name} (${d.rate*100}%)`; }).join('')} `; const morphologyPanelHtml = ` <div class="morphology-grid"> <div class="morphology-info-row"> <div class="morph-badge"><i class="fas fa-infinity"></i> Origin: <strong>${viewModel.lg_level_1}</strong></div> <div class="morph-badge"><i class="fas fa-timeline"></i> Lineage: <strong>${viewModel.lg_level_2}</strong></div> <div class="morph-badge"><i class="fas fa-droplet"></i> Variant: <strong>${viewModel.lg_level_3}</strong></div> </div> <div class="morphology-info-row"> <div class="morph-badge"><i class="fas fa-child"></i> Body: <strong>${viewModel.bodyType}</strong></div> <div class="morph-badge"><i class="fas fa-ruler-vertical"></i> Size: <strong>${viewModel.size}cm</strong></div> <div class="morph-badge"><i class="fas fa-venus-mars"></i> Common: <strong>${commonGenderTag(viewModel.commonGender)}</strong></div> </div> ${(skinSwatches || furSwatches) ? `<div class="swatch-container">${skinSwatches}${furSwatches}</div>` : ''} ${traitsHtml ? `<div class="tags-container mt-2">${traitsHtml}${geneListHtml}</div>` : ''} <div class="morphology-info-row"> <div class="morph-badge"><i class="fas fa-apple-whole"></i>Harvest: <strong>${itemHtmlLite}</strong></div> </div> </div> `; const attrGrid = viewModel.displayAttributes.map(attr => { const barHtml = `<div class="stat-progress"> <div class="progress-bar"> <div class="progress-fill ${attr.color}" style="width: ${attr.pct}%"></div> </div> </div>`; return Utils.html.statCard(attr.label, attr.val, barHtml); }).join(''); const coreAttrGrid = setup.coreStats.map(attr => { const rank = mStandard.getRank(attr); const barHtml = `<div class="stat-progress"> <div class="progress-bar"> <div class="progress-fill rank-${rank}" style="width: 100%"></div> </div> </div>`; const label = `<div class="growth-lr" style="width: 100%; display: flex; justify-content: space-between;"><span>${setup.coreStatData[attr].shortName}</span><span class="rank-${rank}" style="border-radius: 4px; font-size: 17px; padding: 0 6px;">${rank}</span></div>`; return Utils.html.statCard(label, barHtml); }).join(''); const loreUnlockedCount = viewModel.lore.filter(e => e.isUnlocked).length; const loreHtml = viewModel.lore.map(entry => { const unlocked = entry.isUnlocked; return ` <div class="card-lore ${unlocked ? '' : 'locked'}"> <div class="card-lore-header"> <div class="card-lore-title">${entry.name}</div> <div class="card-lore-badges"> ${unlocked ? mkGenderTag(entry.gender) : ''} ${mkSourceTag(entry.source)} ${mkStatusTag(unlocked)} </div> </div> <div class="card-lore-text">${entry.desc}</div> ${!unlocked ? `<div class="card-lore-condition unmet">${entry.reqText}</div>` : ''} </div>`; }).join(''); const mechUnlockedCount = viewModel.eros.filter(e => e.isUnlocked).length; const mechHtml = viewModel.eros.map(m => { let unlocked = m.isUnlocked; return ` <div class="card-lore card-eros ${unlocked ? '' : 'locked'}"> <div class="card-lore-header"> <div class="card-lore-title">${unlocked ? m.name : '???'}</div> <div class="card-lore-badges"> ${unlocked ? mkGenderTag(m.gender) : ''} ${mkSourceTag(m.source)} ${mkStatusTag(unlocked)} </div> </div> <div class="card-lore-text">${m.desc}</div> ${!unlocked ? `<div class="card-lore-condition unmet">${m.reqText}</div>` : ``} ${unlocked ? `<div class="mechanic-tags mt-1">${m.tags.map(t => `<span class="mechanic-tag">${t}</span>`).join('')}</div>` : ``} </div> `; }).join(''); const initialIndex = viewModel.visuals.findIndex(v => v.isUnlocked) !== -1 ? viewModel.visuals.findIndex(v => v.isUnlocked) : 0; const thumbsHtml = viewModel.visuals.map((v, idx) => { const uniqueThumbId = "thumb_" + idx + "_" + Math.random().toString(36).substr(2, 9); setTimeout(() => { if (document.getElementById(uniqueThumbId)) { setup.setCryptoImage(`#${uniqueThumbId}`, v.src); } }, 10 + idx * 2); return ` <div class="gallery-thumb ${v.isUnlocked ? '' : 'locked'} ${idx === initialIndex ? 'active' : ''}" onclick="window.speciesbestiaryAPI.switchGalleryImage('${viewModel.id}', ${idx})" title="${v.name}"> ${v.isUnlocked ? `<img id="${uniqueThumbId}" src="${v.src}">` : ''} </div>` }).join(''); const currentImg = viewModel.visuals[initialIndex] || { src: '', isUnlocked: false, reqText: 'No Data' }; const mainViewHtml = this.generateMainImageHtml(currentImg); this.dom.body.innerHTML = ` <div class="species-illustration"> <div class="gallery-container" id="gallery-root"> <div class="gallery-viewport" id="gallery-viewport"> ${mainViewHtml} </div> <div class="gallery-controls"> ${thumbsHtml} </div> <div class="gallery-info-bar" id="gallery-info"> <div class="flex-row-sb1"> <strong>${currentImg.isUnlocked ? currentImg.name : 'VEILED FORM'}</strong> <span class="text-fs075">Mastery: ${viewModel.researchProgress}%</span> </div> <div class="mt-1 text-fs075" style="color: ${currentImg.isUnlocked ? 'var(--text-secondary)' : 'var(--accent-red-light)'}"> ${currentImg.isUnlocked ? (currentImg.desc || '...') : `${currentImg.reqText}`} </div> </div> </div> <div class="bestiary-speech-bubble"> <div class="speech-text" id="speech-text">${viewModel.catchphrase}</div> </div> </div> <div class="species-stats"> ${Utils.html.section('Morphology & Color', morphologyPanelHtml)} ${Utils.html.section('Growth Potential', `<div class="bestiary-info-statsgrowth">${coreAttrGrid}</div>`)} <!-- ${Utils.html.section('Combat Attributes', `<div class="card-list grid-22">${attrGrid}</div>`)} --> <!-- ${Utils.html.section('Drops & Harvest', `<div class="tags-container">${itemHtml}</div>`)} --> ${Utils.html.section(`Lore & Nature`, `<div class="card-list grid-18">${loreHtml}</div>`, `Mastery: ${loreUnlockedCount}/${viewModel.lore.length} • Summonings: ${viewModel.summonProgress.current}` )} ${Utils.html.section(`Eros & Desires`, `<div class="card-list grid-18">${mechHtml}</div>`, `Mastery: ${mechUnlockedCount}/${viewModel.eros.length} • Interactions: ${viewModel.actionProgress.current}` )} </div> `; this.currentViewModel = viewModel; } generateMainImageHtml(imgData) { const uniqueId = "gallery_img_" + Math.random().toString(36).substr(2, 9); setTimeout(() => { if (document.getElementById(uniqueId)) { setup.setCryptoImage(`#${uniqueId}`, imgData.src); } }, 50); if (imgData.isUnlocked) { return `<img id="${uniqueId}" class="gallery-main-img" src="images/ui/loading.gif" alt="${imgData.name}">`; } else { return ` <img id="${uniqueId}" class="gallery-main-img" src="images/ui/loading.gif" style="filter: blur(15px); opacity: 0.5;"> <div class="gallery-locked-overlay"> <div class="gallery-locked-icon"><i class="fas fa-lock"></i></div> <div>LOCKED</div> <div class="text-fs075 mt-2">Satisfy conditions to view</div> </div> `; } } switchGalleryImage(speciesId, index) { if (!this.currentViewModel || this.currentViewModel.id !== speciesId) return; const imgData = this.currentViewModel.visuals[index]; if (!imgData) return; const viewport = document.getElementById('gallery-viewport'); viewport.innerHTML = this.generateMainImageHtml(imgData); const infoBar = document.getElementById('gallery-info'); infoBar.innerHTML = ` <div class="flex-row-sb1"> <strong>${imgData.isUnlocked ? imgData.name : 'LOCKED'}</strong> <span class="text-fs075">Progress: ${this.currentViewModel.galleryStats.unlocked}/${this.currentViewModel.galleryStats.total}</span> </div> <div class="mt-1 text-fs075" style="color: ${imgData.isUnlocked ? '#aaa' : '#d64541'}"> ${imgData.isUnlocked ? (imgData.desc || '...') : `${imgData.reqText}`} </div> `; const thumbs = document.querySelectorAll('.gallery-thumb'); thumbs.forEach((t, i) => { if (i === index) t.classList.add('active'); else t.classList.remove('active'); }); } updateGlobalStats() { let total = Object.keys(setup.speciesDB).length; let disc = Object.values(setup.speciesDB).filter(m => (this.state.speciesSave[m.id] && this.state.speciesSave[m.id].status !== 'unknown')).length; this.dom.stats.disc.textContent = disc; this.dom.stats.total.textContent = total; this.dom.stats.bar.style.width = ((disc/total)*100) + "%"; } } window.speciesbestiaryAPI = new speciesbestiary(); })(); <</script>> <</done>>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<includeplus "Bestiary">> </div> <<include "UI_RightBar">> </div> </div>
<div class="container build-container"> <div class="overview-panel" style="width: 12rem;"> <div class="originbar-header"> <div class="originbar-title">Build Workshop</div> </div> <div class="nav-list" style="width: 11rem;"> <<for _wing range Object.keys($buildings)>> <<capture _wing>> <<link '<div class="nav-tab" @id="_wing"><<= setup.bdwingdata[_wing]["uiname"]>></div>'>> <<ToggleTags "nav-tab" _wing>><<set _currenwing = _wing>><<unset _currenzone>><<redo "zones">> <<if Flag('tutorialWing')>> <<SetFlag 'tutorialWing' false >> <<if $("#tutorialWing").length>> <<remove "#tutorialWing">> <</if>> <<SetFlag 'tutorialZone'>> <<redo "tutorial">> <</if>> <</link>> <</capture>> <</for>> </div> </div> <<do tag "zones">> <div class="facility-slots-panel"> <<if def _currenwing>> <h2><<= setup.bdwingdata[_currenwing]["name"][$lang]>></h2> <h4><<= setup.bdwingdata[_currenwing]["desc"][$lang]>></h4> <<if visited() <= 1 >> <</if>><div class="zone-list card-list p-2 bg-body"> <<for _zone range Object.keys($buildings[_currenwing])>> <<set _zonefull = $buildings[_currenwing][_zone] >> <<capture _zone _zonefull>> <div class="facility-card" @id="_zone" @style="'background-size: contain; background: ' + setup.mapBuildings[_zonefull.id].bg;"> <<set _linktext = getZoneData(_zone,"name")>> <<set _linkpic = `<div class="csslink" id="facility-image">${_linktext}</div>` >> <<link _linkpic>> <<if _zonefull.id === "bastion">> <<SimpleUI "Battle_Deploy" 90>> <<else>> <<set _currenzone = _zonefull>><<redo "zones slots">> <<if Flag('tutorialZone')>> <<SetFlag 'tutorialZone' false >> <<if $("#tutorialZone").length>> <<remove "#tutorialZone">> <</if>> <<SetFlag 'tutorialUnit'>> <<redo "tutorial">> <</if>> <</if>> <</link>> <div class="zones-stat-item"> <span id="bdprogress">Lv: _zonefull.lv <progress @value="_zonefull.lv" @max="getZoneData(_zonefull.id,'maxlv')"></progress><<= "(" + _zonefull.lv + "/" + getZoneData(_zonefull.id,'maxlv') + ")">></span> </div> <div class="buttons-group-row" style="padding: 0.5rem 0;"> <<if _zonefull.lv >= getZoneData(_zone,"maxlv")>> <<button "Maximum">><</button>> <<else>> <<set _zonecost = getZoneData(_zone,_zonefull.lv+1,'cost')>> <<capture _zonecost>> <<set _buttontext = _zonefull.lv == 1? '🔺Upgrade' : '🔺Upgrade' >> <<button _buttontext>> <<run setup.Popup("<<ZoneUpgrade _zonefull _zonecost>>")>> <</button>> <</capture>> <</if>> <<button "🔨Build">> <<if _zonefull.id === "bastion">> <<SimpleUI "Battle_Deploy" 90>> <<else>> <<set _currenzone = _zonefull>><<redo "zones slots">> <<if Flag('tutorialZone')>> <<SetFlag 'tutorialZone' false >> <<if $("#tutorialZone").length>> <<remove "#tutorialZone">> <</if>> <<SetFlag 'tutorialUnit'>> <<redo "tutorial">> <</if>> <</if>> <</button>> </div> </div> <</capture>> <</for>> </div><</if>> </div> <</do>> <<do tag "zones slots">> <div class="facility-slots-panel"><<if def _currenzone>> <h4><<= getZoneData(_currenzone.id,"name")>></h4> <p><<= getZoneData(_currenzone.id,"desc")>></p> Total Effect: <div class="stat-item"><<= EffectUtils.autoDesc(getZoneData(_currenzone.id,_currenzone.lv,"effect"))>></div> <<= EffectUtils.zoneAll(_currenzone).desc>> <div class="zone-list card-list p-2 bg-body"> <<for _index range $buildingEffects["slots"][_currenzone.id]>> <<set _currenunit = _currenzone[_index] >> <<capture _index _currenunit _currenzone>> <<if _currenunit>> <div class="facility-slot-item filled"> <p><<= getUnitData(_currenunit.id, _currenunit.lv, "name")>></p> <p>Lv:<<= _currenunit.lv>></p> <<button "❌Demolish">> <<run setup.Popup("<<BuildingDemolish _currenunit _index>>")>> <</button>> <<button "🔺Upgrade">> <<run setup.Popup("<<BuildingUpgrade _currenunit _index>>")>> <</button>> </div> <<else>> <div class="facility-slot-item" @id="'unit3'+ _index"> <<button "⚒️Build">> <<if Flag('tutorialUnit')>> <<SetFlag 'tutorialUnit' false >> <<if $("#tutorialUnit").length>> <<remove "#tutorialUnit">> <</if>> <</if>> <<run setup.Popup("<<BuildingNew _currenzone _index>>")>> <</button>></div> <</if>> <</capture>> <</for>> </div> <</if>> </div> <</do>> </div> <<if Flag('tutorial') == 11>> <<set $domain.maps[1] = "Map_Vale02" >> <<pickup $storage "wood" 20 "food" 20 "gold" 200>> <<QuestActive "main_rebuild">> <<run Msg.add('notice', "Hawkridge Vale is secure! Garvin’s gathered resources are stored in the warehouse. With the valley revitalized by a mysterious force, it’s time to restore it. <br>Activate quest: Resettle Refugees and Rebuild Hawkridge Vale.")>> <<SetFlag 'tutorial' 12>> <<SetFlag 'endintro'>> <<SetFlag 'tutorialWing'>> <<run Msg.notify("Unlock [Hawkridge Vale] Map")>> <<run Msg.notify("Unlock [Building Workshop] Building")>> <<run Msg.notify("Unlock [Verdant Wilds] [Hawkridge Hamlet] [Rockvein Mines] Zone")>> <<run Msg.notify("Activate [Vale Rebuild] Quest")>> <<run Msg.notify("+ [20 Wood] + [20 Food] + [200 Good]")>> <</if>> <<do tag "tutorial">> <<done>> <<if Flag('tutorialWing')>> <<SetFlag 'tutorialWing' false>> <<append "#hawkridgevale">> <<set _tutorl = [ { el: '#hawkridgevale', text: "Wings → Zones → Units, the building system is a three-tier structure. Clicking expands the next level of content. This is Wings."}, { el: '#verdant_wilds', text: "Wings → Zones → Units. This is Zones. Clicking expands the next level of content."}, { el: '#unit30', text: "Wings → Zones → Units. This is Units. Clicking expands the next level of content."} ] >> <<TutorList _tutorl>> <</append>> <</if>> <</done>> <</do>>
<<widget TowerInfo>> <<set _arrowConfig = getUnitData(_args[0].id, _args[0].lv, 'arrowRef') >> <<set _unitConfig = setup.arrow[_arrowConfig.type]['levels'][_arrowConfig.lv]['behavior']>> <<= getUnitData(_args[0].id,'name')>><br> <<= getUnitData(_args[0].id,'desc')>><br> Lv: _args[0].lv<br>Arrow<br> Arrow Type: _unitConfig.type<br> Arrow lv: _unitConfig.lv<br> Physical Damage: <<= _unitConfig['physicalDamage']>><br> Fire Rate: <<= _unitConfig['fireRate']>><br> Projectile Speed: <<= _unitConfig['projectileSpeed']>> <</widget>> <<widget BuildUnitByIndexId>> <<set _bdunit = setup.bdUnitData[_args[1]] >> <<set _wfCost = getUnitData(_bdunit.id, 1, "workforce") || 0>> <<if ($domain.workforceUsed + _wfCost) > $domain.getWorkforce()>> <<run Msg.flash(L.moreworkforce)>> <<elseif !$storage.compare(getUnitData(_bdunit.id, 1, "cost"))>> <<run Msg.flash(L.moreresources)>> <<else>> <<= $storage.unmerge(getUnitData(_bdunit.id, 1, "cost"))>> <<run changeEffects(0, 1, _bdunit.type, _args[0], _args[1])>> <<run Msg.notify('Building constructed!');>> <<unset _showunit>> <<redo "build zone unit">> <</if>> <</widget>> <<widget UpgradeUnitByIndexId>> <<set _bdunit = setup.bdUnitData[_args[1]] >> <<set _bdzone = setup.bdZoneData[_bdunit.type]>> <<set _bdzonedata = $buildings[_bdzone.type][_bdunit.type]>> <<set _nowlv = _bdzonedata[_args[0]].lv >> <<set _newlv = _nowlv +1 >> <<set _wfCost = (getUnitData(_bdunit.id, _newlv, "workforce") || 0) - (getUnitData(_bdunit.id, _nowlv, "workforce") || 0)>> <<if ($domain.workforceUsed + _wfCost) > $domain.getWorkforce()>> <<run Msg.flash(L.moreworkforce)>> <<elseif !$storage.compare(getUnitData(_bdunit.id, _newlv, "cost"))>> <<run Msg.flash(L.moreresources)>> <<else>> <<= $storage.unmerge(getUnitData(_bdunit.id, _newlv, "cost"))>> <<run changeEffects(_nowlv, _newlv, _bdunit.type, _args[0], _args[1])>> <<run Msg.notify('Building Upgraded!');>> <<set _showunit = _bdzonedata[_args[0]] >> <<redo "build zone unit">> <</if>> <</widget>> <<widget DemolishUnitByIndexId>> <<set _bdunit = setup.bdUnitData[_args[1]] >> <<if (ndef _demolishnotice) || _demolishnotice === false>> <<set _demolishnotice = true >> <<run Msg.flash('Dismantling provides no refund. Are you sure? Click again to confirm.');>> <<else>> <<run changeEffects(1, 0, _bdunit.type, _args[0], _args[1])>> <<run Msg.notify('Building has been demolished!');>> <<set _demolishnotice = false >> <<unset _showunit>> <<redo "unit build zone">> <</if>> <</widget>> <<widget UpgradeZoneById>> <<set _zoneId = _args[0] >> <<set _zoneData = setup.bdZoneData[_zoneId]>> <<set _wingId = _zoneData.type >> <<set _zoneVar = $buildings[_wingId][_zoneId] >> <<set _zonecost = getZoneData(_zoneId, _zoneVar.lv+1, "cost")>> <<ZoneUpgrade _zoneVar _zonecost>> <</widget>> <<widget ZoneUpgrade>> <<set _zonevs = _args[0] >> <<set _zonecost = _args[1] >> <<set _oldlv = _args[0].lv >> <<set _newlv = _oldlv + 1>> <<set _wfCost = (getZoneData(_zonevs.id, _newlv, "workforce") || 0) - (getZoneData(_zonevs.id, _oldlv, "workforce") || 0)>> <div class="upgrade-container"> <div class="upgrade-unitname"><<= getZoneData(_zonevs.id,"name")>></div> <div class="upgrade-unitdesc"><<= getZoneData(_zonevs.id,"desc")>></div> <table class="upgrade-table"> <thead> <tr> <th class="attribute">Attribute</th> <th>Current Level</th> <th>Next Level</th> </tr> </thead> <tbody> <tr> <td class="attribute">Level</td> <td><<= _zonevs.lv>></td> <td class="upgrade-highlight"> <<if _oldlv >= getZoneData(_zonevs.id,"maxlv")>> - <<else>> <<= _zonevs.lv+1>> <</if>> </td> </tr> <tr> <td class="attribute">Effect</td> <td><<= EffectUtils.autoDesc(getZoneData(_zonevs.id, (_zonevs.lv), "effect"))>></td> <td class="upgrade-highlight"> <<if _oldlv >= getZoneData(_zonevs.id,"maxlv")>> - <<else>> <<= EffectUtils.autoDesc(getZoneData(_zonevs.id, (_zonevs.lv +1), "effect"))>> <</if>> </td> </tr> <tr> <td class="attribute">Upgrade Cost</td> <td>-</td> <td> <<if _oldlv >= getZoneData(_zonevs.id,"maxlv")>> - <<else>> <<for _res, _amount range _zonecost>> <span class="res-icon res-_res.type"><<=_res>> x<<=_amount>>,</span> <</for>> <</if>> </td> </tr> </tbody> </table> <<if _oldlv >= getZoneData(_zonevs.id,"maxlv")>> Maximum lv! <<else>> <<button "Upgrade">> <<if ($domain.workforceUsed + _wfCost) > $domain.getWorkforce()>> <<run Msg.flash(L.moreworkforce)>> <<elseif !$storage.compare(_zonecost)>> <<run Msg.flash(L.moreresources)>> <<else>> <<= $storage.unmerge(_zonecost)>> <<run changeEffects(_zonevs.lv, (_zonevs.lv + 1), _zonevs.id)>> <<if setup.isCurrentView("Battle")>><<run recalculateBattleStats('upgrade')>><</if>> <<redo>><<run setup.Popup.close();>> <</if>> <</button>> <</if>> </div> <</widget>> <<widget BuildingNew>> <<set _chunits = Object.keys(setup.bdUnitData).filter(key => setup.bdUnitData[key].type === _args[0].id)>> <div class="buildnew-container"> <<for _unitId range _chunits>> <<set _newsr = getUnitData(_unitId, 1, "cost") >> <<set _wfCost = getUnitData(_unitId, 1, "workforce")>> <<capture _unitId _args[0] _args[1] _newsr _wfCost>> <<set _neweff = getUnitData(_unitId, 1, "effect") >> <div class="buildnew-card"> <div class="upgrade-unitname"><<= getUnitData(_unitId, 1, "name")>><<= setup.bdUnitData[_unitId].jobs ? " [+Job]": "">></div> <div class="upgrade-unitdesc">Description: <<= getUnitData(_unitId, "desc")>></div> <div class="unit-effect">Unit Effect: <<= EffectUtils.autoDesc(_neweff)>></div> <div class="buildnew-cost">Build Cost: <span class="upgrade-highlight"> <<for _res, _amount range _newsr>> <span class="res-icon res-_res.type"><<=_res>> x<<=_amount>>,</span> <</for>> <<if _wfCost > 0>> workforce x<<= _wfCost>><</if>> </span> </div> <<button "Build">> <<set _wfCost = getUnitData(_unitId, 1, "workforce") || 0>> <<if ($domain.workforceUsed + _wfCost) > $domain.getWorkforce()>> <<run Msg.flash(L.moreworkforce)>> <<elseif !$storage.compare(_newsr)>> <<run Msg.flash(L.moreresources)>> <<else>> <<= $storage.unmerge(_newsr)>> <<run changeEffects(0, 1, _args[0].id, _args[1], _unitId)>> <<redo>><<run setup.Popup.close();>> <</if>> <</button>> </div> <</capture>> <</for>> </div> <</widget>> <<widget BuildingUpgrade>> <<set _oldlv = _args[0].lv >> <<set _newlv = _oldlv + 1 >> <<set _slotIndex = _args[1] >> <<set _unitId = _args[0].id >> <<set _zoneId = getUnitData(_unitId, "type") >> <<set _wfCost = getUnitData(_unitId, _newlv, "workforce") - getUnitData(_unitId, _oldlv, "workforce")>> <<set _oldName = getUnitData(_unitId, _oldlv, "name") >> <<set _newName = getUnitData(_unitId, _newlv, "name") >> <<set _oldeffect = getUnitData(_unitId, _oldlv, "effect") >> <<set _upgradecost = getUnitData(_unitId, _newlv, "cost") >> <<set _upgradeeffect = getUnitData(_unitId, _newlv, "effect") >> <div class="upgrade-container"> <div class="upgrade-unitname"><<= _newName >><<= setup.bdUnitData[_unitId].jobs ? " [+Job]": "">></div> <div class="upgrade-unitdesc"><<= getUnitData(_unitId, "desc")>></div> <table class="upgrade-table"> <thead> <tr> <th class="attribute">Attribute</th> <th>Current (<<= _oldName>>)</th> <th>Next (<<= _newName>>)</th> </tr> </thead> <tbody> <tr> <td class="attribute">Level</td> <td><<= _oldlv>></td> <td class="upgrade-highlight"> <<if _oldlv >= getUnitData(_unitId,"maxlv")>> - <<else>> <<= _newlv>> <</if>> </td> </tr> <tr> <td class="attribute">Effect</td> <td><<= EffectUtils.autoDesc(_oldeffect)>></td> <td class="upgrade-highlight"> <<if _oldlv >= getUnitData(_unitId,"maxlv")>> - <<else>> <<= EffectUtils.autoDesc(_upgradeeffect)>> <</if>> </td> </tr> <tr> <td class="attribute">Upgrade Cost</td> <td>-</td> <td> <<if _oldlv >= getUnitData(_unitId,"maxlv")>> - <<else>> <<for _res, _amount range _upgradecost>> <span class="res-icon res-_res.type"><<=_res>> x<<=_amount>>,</span> <</for>> <<if _wfCost > 0>> workforce x<<= _wfCost>><</if>> <</if>> </td> </tr> </tbody> </table> <<if _oldlv >= getUnitData(_unitId,"maxlv")>> Maximum lv! <<else>> <<button "Upgrade">> <<if ($domain.workforceUsed + _wfCost) > $domain.getWorkforce()>> <<run Msg.flash(L.moreworkforce)>> <<elseif !$storage.compare(_upgradecost)>> <<run Msg.flash(L.moreresources)>> <<else>> <<= $storage.unmerge(_upgradecost)>> <<run changeEffects(_oldlv, _newlv, _zoneId, _slotIndex, _unitId)>> <<redo>><<run setup.Popup.close();>> <</if>> <</button>> <</if>> </div> <</widget>> <<widget BuildingDemolish>> <<set _oldlv = _args[0].lv >> <<set _slotIndex = _args[1] >> <<set _unitId = _args[0].id >> <<set _zoneId = getUnitData(_unitId,"type") >> <<set _refundWF = getUnitData(_unitId, _oldlv, "workforce")>> <div> <p>Curren Unit: <<= getUnitData(_unitId,"name")>> </p> <p>Curren lv: _oldlv </p> <p>Curren Effect: <<= EffectUtils.autoDesc(getUnitData(_unitId,_oldlv,"effect"))>></p> <p>Demolition does not return resources, but will refund x<<= _refundWF>> workforce.</p> <<button "Demolish">> <<run changeEffects(_oldlv, 0, _zoneId, _slotIndex, _unitId)>> <<redo>><<run setup.Popup.close();>> <<run JobSystem.getPreview()>> <</button>> </div> <</widget>>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<includeplus "Building">> </div> <<include "UI_RightBar">> </div> </div>
<div class="calendar-container" id="gameCalendar"> <div class="calendar-header"> <div class="month-year" id="monthYear"></div> <div class="nav-buttons"> <button class="nav-btn" id="prevMonth">«</button> <button class="nav-btn" id="todayBtn">•</button> <button class="nav-btn" id="nextMonth">»</button> </div> </div> <div class="weekdays"> <div class="weekday">Sun</div> <div class="weekday">Mon</div> <div class="weekday">Tue</div> <div class="weekday">Wed</div> <div class="weekday">Thu</div> <div class="weekday">Fri</div> <div class="weekday">Sat</div> </div> <div class="calendar-grid" id="calendarGrid"></div> </div> <<done>> <<run setup.Calendar.init()>> <</done>>
<<widget "CardPop">> <<set $cards = [] >> <<silent>> <<CardLottery _args[0] _args[1] _args[2]>> <</silent>> <<run Dialog.create("cards", "fullpop")>> <<run Dialog.wiki("<<CardShow>>")>> <<run Dialog.open()>> <</widget>> <<widget "CardLottery">> <<set _cardnum = _args[2] ? _args[2] : 1>> <<for _i range _cardnum>> <<switch MonsterBreeder.run(_args[0], _args[1])>> <<case 1>> <<Get2 _args[0] _args[1] _i>> <<case 2 3>> <<Get3 _args[0] _args[1] _i>> <<case 4>> <<Get4 _args[0] _args[1] _i>> <<case 5 6 7 8>> <<Get5 _args[0] _args[1] _i>> <<default>> <<Get1 _i>> <</switch>> <</for>> <</widget>> <<widget "CardShow">> <div class="card-row" id="card-row"> <<for _i, _cardData range $cards>> <<capture _cardData>> <div class="card-container"> <div class="card-inner" @id='"card-" + _cardData.id'> <div class="card-back">Flip Card</div> <div @class='"card-front card-type-" + _cardData.type'> <div class="card-content-area" style="width: 100%;"> <<if _cardData.type == "character">> <<CharacterCardShow _cardData.char>> <<elseif _cardData.type != "none">> <<ItemCardShow _cardData.itemId 12 _cardData.amount>> <<else>> <div style="padding: 2rem; text-align:center;">Empty</div> <</if>> </div> <div class="card-cardlabel"> <h2><<= _cardData.type.toUpperFirst()>></h2> <span @class='"card-affix card-" + _cardData.affix'> <<= _cardData.affix>> </span> </div> <div class="card-select"> <<if _cardData.type === "character">> <<if Flag('endispatch') == 2>> <<HoverTip "Take he/she into the barony, counted as population but not actionable.">> <<button "To Barony">> <<run PopSystem.add(_cardData.char, "barony");>> <<run Dialog.close()>> <</button>> <</HoverTip>> <</if>> <div id="totemplebtn"><<button "To Temple">> <<ConfirmCardSelection _cardData>> <<run Dialog.close()>> <</button>></div> <<else>> <<button "Select">> <<ConfirmCardSelection _cardData>> <<run Dialog.close()>> <</button>> <</if>> </div> </div> </div> </div> <</capture>> <</for>> </div> <<done>> <<script>> for (let i = 0; i < State.variables.cards.length; i++) { const card = document.querySelector(`#card-${i}`); if (card) { card.addEventListener('click', (e) => { if (e.target.tagName !== 'BUTTON') { card.classList.toggle('card-flipped'); } }); } } <</script>> <<toggleclass ".card-inner" "card-flipped">> <</done>> <</widget>> <<widget "ItemPicShow">> <<set _item = _args[0] >> <<set _itemtype = getiteminfo(_args[0],"type")>> <<set _itemId = getiteminfo(_item,"id") >> <<showframe _itemtype _itemId _args[1]>> <</widget>> <<widget "ItemCardShow">> <<set _item = _args[0] >> <<set _itemtype = getiteminfo(_args[0],"type")>> <div class="item-detail-card"> <div class="item-ornate-frame"> <div id="item-detailImg" class="item-featured"> <<set _itemId = getiteminfo(_item,"id") >> <<showframe _itemtype _itemId _args[1]>> </div> </div> <div class="item-title"> <h2 id="item-itemName" class="item-illuminated-text"> <<= getiteminfo(_item,"name")>> </h2> <div class="item-title-decoration"></div> </div> <div class="item-scroll-container"> <div class="item-parchment"> <p id="item-itemDesc" class="item-calligraphic-text"> <<= getiteminfo(_item,"desc")>> <div class="item-heraldic-overlay"> <<if getiteminfo(_item,"duration")>> <span>⏳: <<if getiteminfo(_item,"duration") == -1>><<= setup.langbank.permanent[$lang]>><<else>><<= getiteminfo(_item,"duration")>><<= setup.langbank.day[$lang]>><</if>></span> <</if>> <span><<= setup.langbank.quality[$lang]>>: <<= getiteminfo(_item,"lv")>> </span> <span class="item-rarity-star"> <<NumToStars `getiteminfo(_item,"lv")`>> </span> </div> <div class="item-heraldic-overlay"> <span><<= setup.langbank.quantity[$lang]>>: x<<if _args[2]>><<= _args[2]>><<else>><<= $storage.count(_item)>><</if>></span> <span><<= setup.langbank.price[$lang]>>: <<= getiteminfo(_item,"price")>>g</span> <span><<= setup.langbank.type[$lang]>>: <<= _itemtype>></span> </div> </p> </div> </div> <</widget>> <<widget "CharacterCardShow">> <<set _item = _args[0] >> <div class="item-detail-card"> <div class="character-ornate-frame"> <div id="item-detailImg" class="item-featured"> <<if _args[0].type == "wrong">> <<set _wrongCg = setup.ImagePath + _args[0].img >> <<cimg _wrongCg>> <<else>> <<HeadImageByID _args[0] 168>> <</if>> </div> </div> <div class="item-title"> <div class="character-decoration"> <span><<= _args[0].name>> <<= setup.gender2pic[_args[0].gender]>></span> <span><<= _args[0].race.toUpperFirst()>></span> <span><<= setup.speciesDB[_args[0].rankId].baseInfo.name>></span> <span>Look: <<= _args[0].getLook>></span> <span>Daily: <<= _args[0].dailyAP.toFixed(1)>>❤️</span> <span>Growth: <<= _args[0].getGrowth>></span> </div> </div> <div class="item-scroll-container"> <div class="item-parchment"> <div id="item-itemDesc" class="item-calligraphic-text"> <<RadarImage _item>> <div class="attr-other-attributes"> <div class="attr-grid"> <<TraitsList _args[0]>> </div> </div> <div class="item-heraldic-overlay"> <span class="item-rarity-star"></span> </div> </div> </div> </div> <</widget>> <<widget "Get1">> <<set $cards[_args[0]] = { id: _args[0], type: 'none', affix: '', name: 'Nothing' }>> <</widget>> <<widget "Get2">> <<set _getit = getRandomItem("sundries")>> <<set $cards[_args[2]] = { id: _args[2], type: 'sundries', affix: 'Junk', itemId: _getit.key, amount: 1 }>> <</widget>> <<widget "Get3">> <<set _getit = getRandomItem("resources")>> <<set _getnum = Math.clamp(random(_args[0].getStatFinal("cha"), _args[1].getStatFinal("cha")), 5, 20)>> <<if _getit.key == "gold">><<set _getnum *= 10>><</if>> <<set _isDouble = random(1,100) < 10>> <<set $cards[_args[2]] = { id: _args[2], type: 'resources', affix: _isDouble ? 'Double' : 'Common', itemId: _getit.key, amount: _isDouble ? _getnum * 2 : _getnum }>> <</widget>> <<widget "Get4">> <<set _getrandom = random(1,100)>> <<set _lvrandom = Math.floor(random(_args[0].getStatFinal("wil"), _args[1].getStatFinal("wil")) / 10)>> <<set _affix = 'Common', _getnum = 1, _lvMulti = 1, _rarity = 1>> <<if _getrandom < 80>> <<elseif _getrandom < 90>> <<set _affix = 'Double', _getnum = 2>> <<elseif _getrandom < 95>> <<set _affix = 'Critical', _lvMulti = 2, _rarity = 2>> <<else>> <<set _affix = 'Legendary', _lvMulti = 3, _rarity = 3>> <</if>> <<set _getlv = Math.clamp(_lvrandom * _lvMulti, 1, 5)>> <<set _getit = getRandomItem("alchemy", _rarity, _getlv)>> <<set $cards[_args[2]] = { id: _args[2], type: 'alchemy', affix: _affix, itemId: _getit.key, amount: _getnum }>> <</widget>> <<widget "Get5">> <<set _get5Random = Math.random() >> <<set _get5Relation = setup.BloodlineManager.getRelationDefiance(_args[0], _args[1]) >> <<if _get5Random < _get5Relation >> <<MergeWrong _args[0] _args[1]>> <<else>> <<MergeMonsters _args[0] _args[1] $isHybridBreeding>> <</if>> <<if _args[3] && _args[3] === "Birth">> <<set _newmonster.look.base = Math.max(_args[0].look.base, _args[1].look.base) + random(-5,5)>> <<run setup.applyStrongestGeneModifiers(_newmonster, [_args[0], _args[1]])>> <</if>> <<set _char = clone(_newmonster)>> <<set $cards[_args[2]] = { id: _args[2], type: 'character', affix: setup.renderRarity(_char.rank), name: _char.name, char: _char, p1_id: _args[0].id, p2_id: _args[1].id }>> <<unset $addmonster>> <</widget>> <<widget "ConfirmCardSelection">> <<set _cardData = _args[0]>> <<switch _cardData.type>> <<case "sundries" "resources" "alchemy">> <<pickup $storage _cardData.itemId _cardData.amount>> <<case "character">> <<AddMonster _cardData.char>> <<set _p1 = getMonsterByID(_cardData.p1_id)>> <<set _p2 = getMonsterByID(_cardData.p2_id)>> <<if _p1>> <<run _p1.setFerClear()>> <<run _p1.offspring.push({id: _cardData.char.id, name: _cardData.char.name})>> <<run _p1.markDirty()>> <</if>> <<if _p2>> <<run _p2.setFerClear()>> <<run _p2.offspring.push({id: _cardData.char.id, name: _cardData.char.name})>> <<run _p2.markDirty()>> <</if>> <<redo "selected summon">> <</switch>> <</widget>> <<widget "RadarImage">> <div class="radar-character-card"> <<RadarImageByChar _args[0]>> <div class="radar-stat-table buttons-group-col"> <<set _infolistRole = _args[0] >> <<for _attr range setup.coreStats>> <<capture _attr _infolistRole _replacecss>> <<set _replacecss = "#radar-"+ _infolistRole.id>> <<HoverTip '<<StatCard _infolistRole _attr>>'>><<AttrShow _infolistRole _attr>><</HoverTip>> <</capture>> <</for>> </div> </div> <</widget>>
<div class="log-container"> <div class="module-bigtitle">Changelogs</div> <div class="log-subtitle">This game is under active development. The latest version is available on Patreon, where I'm working on it full-time. Your support is greatly appreciated!</div> <div class="log-ornament">◈◈◈</div> <div class="log-tabs"> <div class="section-panel"> <div class="section-title">📜Version: v0.62.3 🕐Update Time: 2026.8.16</div> <div class="log-tab-content"> <h4 class="log-section-title">🛠️Fixes:</h4> <ul class="module-ul"> <li>Fixed layout breaking on the summoning page in Firefox (which made the game unplayable—sorry about that!).</li> <li>Fixed various Firefox UI glitches (summoning cards, character list, etc.).</li> <li>Fixed text errors and typos in story dialogues.</li> </ul> </div> </div> <div class="section-panel"> <div class="section-title">📜Version: v0.62.2 🕐Update Time: 2026.8.12</div> <div class="log-tab-content"> <h4 class="log-section-title">🛠️Fixes:</h4> <ul class="module-ul"> <li>Fixed known bugs and glitches.</li> </ul> <h4 class="log-section-title">🌟Changes:</h4> <ul class="module-ul"> <li>Smoothed out performance to cut down lag.</li> <li>Refined tutorial UI/UX.</li> </ul> </div> </div> <div class="section-panel"> <div class="section-title">📜Version: v0.62.1 🕐Update Time: 2026.8.12</div> <div class="log-tab-content"> <h4 class="log-section-title">🛠️Fixes:</h4> <ul class="module-ul"> <li>Bug Fixes: Squashed some known bugs.</li> </ul> <h4 class="log-section-title">🌟Changes:</h4> <ul class="module-ul"> <li>Performance: Optimized loading times.</li> <li>Tech Tree: Moved all training unlocks into the tech tree for central management.</li> <li>Bestiary UI: Redesigned to display more species details.</li> <li>Bestiary Lore: Expanded lore entries for various species.</li> <li>Tutorial: Adjusted layout so it won't block gameplay anymore.</li> </ul> <h4 class="log-section-title">💼Important Notes:</h4> <ul class="module-ul"> <li>Save Notice: Major Bestiary data changes. Old Bestiary data will break or glitch out. Clear your soul data and start a fresh run.<br> Heads up: This patch is mostly an early preview/optimization test. If you already have a solid, satisfying playthrough going, feel free to skip this update and wait until I stabilize the Bestiary. </li> </ul> </div> </div> <div class="section-panel"> <div class="section-title">📜Version: v0.61.1 🕐Update Time: 2026.8.1</div> <div class="log-tab-content"> <h4 class="log-section-title">🌟Changes:</h4> <ul class="module-ul"> <li>[System] Relocated the Hybridization toggle from Settings to the Summoning page with individual per-species checkboxes. Crossbreeding unlocks for each species are now controlled via the Tech Tree.</li> <li>[Character] Added unique character Delphi alongside her storyline events. (Note: Some art assets are still WIP. Day 34-36 )</li> <li>[Species] Introduced the Sirenia species. (Hybrid disabled for now)</li> <li>[Outfit] Reworked the Outfit system to accommodate non-bipedal species.</li> </ul> <h4 class="log-section-title">💼Important Notes:</h4> <ul class="module-ul"> <li>Save Notice: Starting a new game is highly recommended due to updates to storylines, characters, and tech tree data. Your Achievement and Bestiary data are fully preserved.</li> </ul> </div> </div> <div class="section-panel"> <div class="section-title">📜Version: v0.60.2 🕐Update Time: 2026.7.13</div> <div class="log-tab-content"> <h4 class="log-section-title">🛠️Fixes:</h4> <ul class="module-ul"> <li>Fixed Codex character portraits not displaying.</li> </ul> <h4 class="log-section-title">🌟Changes:</h4> <ul class="module-ul"> <li>Revamped Message UI. Now tracks Unread Notices, Pending Events, and Unsubmitted Quests. Heads up: Pending events now block sleeping.</li> <li>Added Blood Moon event and story (Day 14).</li> <li>Added Bestiary save migration for Public/Modded builds. It's experimental—I wouldn't recommend switching versions too often just yet!</li> </ul> <h4 class="log-section-title">💼Important Notes:</h4> <ul class="module-ul"> <li>Save Compatibility: Save compatible with v0.60.X. Your Achievement and Bestiary data are fully preserved.</li> </ul> </div> </div> <div class="section-panel"> <div class="section-title">📜Version: v0.60.1 🕐Update Time: 2026.7.2</div> <div class="log-tab-content"> <h4 class="log-section-title">🛠️Fixes:</h4> <ul class="module-ul"> <li>Fixed various bugs.</li> </ul> <h4 class="log-section-title">🌟Changes:</h4> <ul class="module-ul"> <li>Overhauled the tech tree — now supports tiered unlocks.</li> <li>Advanced story arcs for Stellafall and Hizuki.(Day 8 & Day 33)</li> <li>Expanded map system features.</li> <li>Added a unified event notification system (Beta).</li> </ul> <h4 class="log-section-title">💼Important Notes:</h4> <ul class="module-ul"> <li>Save Compatibility: Due to the major foundation changes, old saves are unfortunately not compatible. You will need to start a new game. Your Achievement and Bestiary data are fully preserved.</li> <li>Life threw a few curveballs lately, hence the delay. Back to regular updates now. Thanks for sticking around and for your patience!</li> </ul> </div> </div> <div class="section-panel"> <div class="section-title">📜Version: v0.59.3 🕐Update Time: 2026.5.27</div> <div class="log-tab-content"> <h4 class="log-section-title">🛠️Fixes:</h4> <ul class="module-ul"> <li>Fixed sync lag for top resource bars.</li> <li>Removed boots from male outfits—they wouldn't fit all species properly without clipping, so they had to go.</li> <li>Fixed Pureblood trait calculation logic.</li> <li>Fixed incorrect war tax day labels in Calendar.</li> <li>Fixed progress bar jitters caused by Mood Essence bug.</li> <li>Various minor bug fixes.</li> </ul> <h4 class="log-section-title">🌟Changes:</h4> <ul class="module-ul"> <li>Added tier badges to traits in the characters list.</li> <li>Optimized loyalty gain logic for training actions.</li> <li>Added "Driven" trait (T3, Devoted + Diligent). Swapped out "Selfless" for "Diligent" in the tree to stop unwanted EXP loss.</li> <li>Added 10 new outfits (7 Female, 3 Male).</li> <li>Tweaked requirements and values for some training actions.</li> </ul> <h4 class="log-section-title">💼Important Notes:</h4> <ul class="module-ul"> <li>Save Compatibility: Save compatible with v0.59.X. Your Achievement and Bestiary data are fully preserved.</li> </ul> </div> </div> <div class="section-panel"> <div class="section-title">📜Version: v0.59.2 🕐Update Time: 2026.5.17</div> <div class="log-tab-content"> <h4 class="log-section-title">🛠️Fixes:</h4> <ul class="module-ul"> <li>Fixed the auto-harvest bug where items weren't dropping or were being harvested twice.</li> <li>Various minor bug fixes.</li> </ul> <h4 class="log-section-title">🌟Changes:</h4> <ul class="module-ul"> <li>Added an Outfit System (Early Version). It's basic for now, but I'll be adding more features and support for Unique characters in future updates.</li> <li>Optimized auto-assignment: Workers will now only be auto-assigned to Leader spots if their match rate is >100%.</li> </ul> <h4 class="log-section-title">💼Important Notes:</h4> <ul class="module-ul"> <li>Save Compatibility: Save compatible with v0.59.1. Your Achievement and Bestiary data are fully preserved.</li> </ul> </div> </div> <div class="log-ornament">◈◈◈</div> </div>
<<for _category, _categorydata range $gamestatus.codexdata>> <<for _entry range _categorydata>> <<if _entry.seal === "seal">> <<silent>> <<include _entry.id>> <<if _condition && _condition()>> <<set _entry.seal = "unseal">> <<set _condition = false>> <</if>> <</silent>> <</if>> <</for>> <</for>> <div class="container codex-nar"> <div class="overview-panel" style="overflow-y: hidden;height: 100%;background: none;width: 12.6rem;"> <div class="originbar-header"> <div class="originbar-title" style="color: var(--text-inverse);">Codex</div> </div> <div class="tome-nav-list nav-list"> <<for _category, _entries range $gamestatus.codexdata>> <div class="category-nav-tab nav-tab" > <<capture _category>> <<csslink setup.codexcategory[_category][$lang] category-nav-tab>> <<set _tempcategory = "#CodexCategory_"+_category >> <<toggleclass _tempcategory 'hidden'>> <</csslink>><</capture>></div> <div @id='"CodexCategory_"+_category' class="hidden"> <<for _entry range _entries>> <<capture _entry>> <<if _entry.seal === "seal">> <<else>> <<link '<div class="entry-item"><<= _entry.title>></div>'>> <<include _entry.id >> <<replace '#codexContent'>> <<= setup.entrycontent[$lang]>> <</replace>> <</link>> <</if>> <</capture>> <</for>> <</for>> </div> </div> <div id="codexContent" class="tome-content"> <<include "CodexEntry_index">> </div> </div>
<<set _entries = [] >> <<set _categoryid = "Attributes" >> <<set _categorylang = { "en": `Attributes` } >> <<set _passages = _codex.filter(function (p) {return p.tags.includes(_categoryid);})>> <<for _tmp range _passages>> <<include _tmp.name>> <<run _entries.push({id: _tmp.name, title: setup.entrytitle[$lang], seal: "seal"})>> <</for>> <<set _entries = _entries.sort()>>
<<set _entries = [] >> <<set _categoryid = "Battle" >> <<set _categorylang = { "en": `Battle` } >> <<set _passages = _codex.filter(function (p) {return p.tags.includes(_categoryid);})>> <<for _tmp range _passages>> <<include _tmp.name>> <<run _entries.push({id: _tmp.name, title: setup.entrytitle[$lang], seal: "seal"})>> <</for>> <<set _entries = _entries.sort()>>
<<set _entries = [] >> <<set _categoryid = "Building" >> <<set _categorylang = { "en": `Building` } >> <<set _passages = _codex.filter(function (p) {return p.tags.includes(_categoryid);})>> <<for _tmp range _passages>> <<include _tmp.name>> <<run _entries.push({id: _tmp.name, title: setup.entrytitle[$lang], seal: "seal"})>> <</for>> <<set _entries = _entries.sort()>>
<<set _entries = [] >> <<set _categoryid = "Characters" >> <<set _categorylang = { "en": `Characters` } >> <<set _passages = _codex.filter(function (p) {return p.tags.includes(_categoryid);})>> <<for _tmp range _passages>> <<include _tmp.name>> <<run _entries.push({id: _tmp.name, title: setup.entrytitle[$lang], seal: "seal"})>> <</for>> <<set _entries = _entries.sort()>>
<<set _entries = [] >> <<set _categoryid = "Notes" >> <<set _categorylang = { "en": `Notes` } >> <<set _passages = _codex.filter(function (p) {return p.tags.includes(_categoryid);})>> <<for _tmp range _passages>> <<include _tmp.name>> <<run _entries.push({id: _tmp.name, title: setup.entrytitle[$lang], seal: "seal"})>> <</for>> <<set _entries = _entries.sort()>>
<<set _codexdata = {}>> <<set _codexcategory = {}>> <<set _codex = Story.filter(function (c) {return c.tags.includes("codex");})>> >> <<set _categories = _codex.filter(function (c) {return c.tags.includes("codexcategory");})>> <<for _tmp range _categories>> <<include _tmp.name>> <<set _codexdata[_categoryid] = _entries >> <<set _codexcategory[_categoryid] = _categorylang >> <</for>> <<include "CodexCategory_Manually">> <<set $gamestatus.codexdata = _codexdata >> <<set setup.codexcategory = _codexcategory >> <<SetFlag 'entry_format' 1>> <<SetFlag 'entry_Characters_griffinknight' 1>>
<center><h1>Calendar</h1> <h2><<= setup.TimeSystem.getCurrentInfo()>> <br>Day $times.total</h2></center> You open your codex, containing all the knowledge you've acquired so far. <br>You realize the hidden dangers of this world - keeping thorough records shall become your greatest weapon <br>when destiny calls! <<include "Calendar">>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<includeplus "Codex">> </div> <<include "UI_RightBar">> </div> </div>
<<widget "Sleep">> <<DailyUpdate>> <<run setup.TimeSystem.passOneDay();>> <<goto "Bedroom_Morning">> <</widget>> <<widget "DailyUpdate">> <<set $domain.wartaxdeadline -= 1 >> <<set $battle.demonattackdeadline -= 1 >> <<set $manage.energy.current = Math.clamp($manage.energy.current + $manage.energy.regen, 1000, $manage.getMaxEnergy()) >> <<set $job.summary = JobSystem.dailyJobs()>> <<set $domain.fooduse = PopSystem.getFoodConsumption() >> <<drop $storage "food" $domain.fooduse >> <<set $messages.messageFast = []>> <<set $messages.isFastMessageShowing = false>> <<set $messages.notices = []>> <<set $slave.slaveList = []>> <<run $slave.slaveMap.clear()>> <<run $chars.templeList.forEach(monsterId => $chars.dataMap.get(monsterId).dailyupdate());>> <<run setup.updatePregnancies()>> <<QuestUpdate>> <</widget>> <<widget "NextDayEvent">> <<if PopSystem.getTotalPop() > 300>><</if>> <<if $storage.count('food') === 0>> <<run Msg.add('notice', L.foodpanic);>> <</if>> <<run setup.EventManager.checkScheduled()>> <</widget>>
<div class="container dispatch-container"> <div class="content-panel"> <<do tag "questslist">> <div class="dispatch-list card-list p-2 bg-body"> <<for _i range $dispatch.quests.length>> <<set _matchCount = filterMonsterByCondition($chars.templeList, $dispatch.quests[_i]).count>> <<cardlink _i _matchCount>> <div class="dispatch-card card-slot" @id="_linkargs[0]"> <div class="dispatch-block-1"> <<= $dispatch.quests[_linkargs[0]].getDifficultyStars()>> <div class="dispatch-matchCount"><<= _linkargs[1]>></div> </div> <div class="dispatch-requirelist"> <<= $dispatch.quests[_linkargs[0]].getRequirementList()>> </div> <div class="dispatch-block-3"> <div class="dispatch-deadline"> <<= L.Avail>>:<<= $dispatch.quests[_linkargs[0]].deadline>><<= "d">> </div> <div class="dispatch-publisher"><<= L.publisher>>:<<= $dispatch.quests[_linkargs[0]].publisher>></div> <div class="dispatch-bounty-item"><<= L.bounty>>: <<= $dispatch.quests[_linkargs[0]].getBountyList()>> </div> </div> </div> <<onclick>> <<set _choiceDispatch = _linkargs[0]>> <<redo "questlist">> <<if _linkargs[1] > 0>> <<run Dialog.create("", "filterresults")>> <<run Dialog.wiki("<<DispatchFilter $dispatch.quests[_linkargs[0]]>>")>> <<run Dialog.open()>> <</if>> <</cardlink>> <</for>> </div> <</do>> </div> <div class="content-panel dispatch" style="overflow: visible;"> <<do tag "questlist">> <<if def _choiceDispatch>> <div class="dispatch-publisherimg">[img[setup.ImagePath +$dispatch.quests[_choiceDispatch].publisherpic]]</div> <div class="dispatch-text"> <span><<= $dispatch.quests[_choiceDispatch].publisher>> :</span><br> <span><<= $dispatch.quests[_choiceDispatch].text>></span><br> <div class="dispatch-close"><<link "❌">><<unset _choiceDispatch>><<redo "questlist">><</link>></div> <div class="dispatch-window"> <<button `langBank("FilterEligible")`>> <<run Dialog.create("", "filterresults")>> <<run Dialog.wiki("<<DispatchFilter $dispatch.quests[_choiceDispatch]>>")>> <<run Dialog.open()>> <</button>> </div> </div> <<else>> <</if>> <</do>> </div> </div>
<<set _newquest = new Quest({})>> <<= _newquest.randomprop("wil",1);>> <<set _newquest.publisher = L.butler>> <<set _newquest.publisherpic = $chars.dataMap.get("butler").img>> <<set _newquest.bounty.gold += 50 >> <<set _newquest.text = L.questtip1>> <<run $dispatch.quests.push(_newquest);>> <<run randomDispatch()>> <<run randomDispatch()>> <<run randomDispatch()>>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<includeplus "Dispatch">> </div> <<include "UI_RightBar">> </div> </div>
<<widget QuestUpdate>> <<nobr>> <<run $dispatch.quests = $dispatch.quests.filter(q => { q.deadline--; return q.deadline > 0; }) >> <<set _addnum = $dispatch.capacity - $dispatch.quests.length >> <<if _addnum > 0>> <<for _i range _addnum>><<run randomDispatch()>><</for>> <</if>> <</nobr>> <</widget>> <<widget DispatchFilter>> <<set _filtergather = filterMonsterByCondition($chars.templeList, _args[0]).ids>> <<set $chars.selected = null>> <div class="itemfilter-title">Filter results</div> <div class="itemfilter"> <div class="itemfilter-list"> <<MapList _filtergather>> </div> <div class="itemfilter-info"> <<do tag "selected">> <<if $chars.selected>> <<Infocard $chars.selected>> <</if>> <</do>> <<if visited() <= 3 >> <div class="itemfilter-requirement-card"> <p><<= L.DispatchNotice1>></p> </div> <</if>> <div class="itemfilter-act"> <<button setup.langbank.Dispatch[$lang]>> <<if !$chars.selected>> <<run Msg.flash(L.selecttarget)>> <<elseif $chars.selected["type"] == "unique">> <<run Msg.flash(L.nospecial)>> <<elseif $chars.selected["worktags"]>> <<run Msg.flash(L.currentlyworking)>> <<else>> <<run $domain.Changes($dispatch.quests[_choiceDispatch].bounty)>> <<run PopSystem.transfer($chars.selected, "temple", "barony")>> <<DelMonster $chars.selected>> <<run $dispatch.quests.deleteAt(_choiceDispatch)>> <<unset _choiceDispatch>> <<set $chars.selected = null>> <<set $summon.slots to [null, null]>> <<set $summon.currentsolt = -1>> <<set $manage.dispatchCount += 1 >> <<redo "questslist topbar">> <<run Dialog.close()>> <</if>> <</button>> <<button "Close">> <<run Dialog.close()>> <</button>> </div> </div> </div> <</widget>>
<<set _jobCache = JobSystem.getPreview()>> <<set setup.territoryData = { energy: { current: `${$manage.energy.current}/${$manage.getMaxEnergy()}`, Regen: `+ ${$manage.energy.regen} / day`, }, population: { barony: PopSystem.getLocationStats("barony").count, temple: PopSystem.getLocationStats("temple").count, workforce: `${$domain.getWorkforce()} (- ${$domain.getUnitsWF()})`, }, resources: { gold: `${$storage.count("gold")} (+ ${_jobCache.totalIncome?.gold || 0})`, food: `${$storage.count("food")} (+ ${_jobCache.totalIncome?.food || 0})`, wood: `${$storage.count("wood")} (+ ${_jobCache.totalIncome?.wood || 0})`, stone: `${$storage.count("stone")} (+ ${_jobCache.totalIncome?.stone || 0})`, ore: `${$storage.count("ore")} (${_jobCache.totalIncome?.ore || 0})`, crystal: `${$storage.count("crystal")} (${_jobCache.totalIncome?.crystal || 0})`, }, defense: { fortitude: $buildingEffects.stats.defense, wrath: $buildingEffects.stats.wrath }, working: { totalSummons: $manage.summon.count, totalFeed: $manage.feed.count, totalHarvest: $manage.harvest.count, totalDispatch: $manage.dispatchCount }, buildings: { unlockedTrait: $tech.enTraits.length, unlockedSpecies: $manage.ownrace.size }, event: { demonAttack: $battle.demonattackdeadline + " days", warTax: $domain.wartaxdeadline + " days", SlaverCaravan: "Tuesday", BlackMarket: "Thursday" } }; >> <<territoryReport>>
<div class="blood-effect"></div> <div class="badend-container"> <div style="text-align: center; font-family: 'Georgia', serif; color: #d1d1d1; padding: 40px;"> <h1 style="color: #8b0000; letter-spacing: 5px; font-size: 2.5em;">THE SANCTUM FALLS</h1> <div style="margin: 30px auto; width: 60%; border-top: 1px solid #444; border-bottom: 1px solid #444; padding: 20px 0;"> <p style="font-size: 1.2em; font-style: italic;">"The guardian has failed. The oath is broken."</p> </div> <p style="margin-top: 20px; line-height: 1.8; color: var(--text-secondary);"> The demonic tide has claimed Hawkridge Vale. <br> The ancient light of your House is extinguished. </p> <p style="margin-top: 20px; line-height: 1.8; color: #777;"> Humanity's final sanctuary lies in ruin. <br> History shall remember you not as a Lord, but as the last ember that dared to defy the Abyss. </p> <div style="margin-top: 40px; font-weight: bold; color: #8b0000;"> THE AGE OF MAN ENDS HERE. </div> <div style="margin-top: 3rem;"> <<link '<div class="buttoncss">Main Menu</div>'>><<goto 'Welcome'>><<run Engine.restart();>><</link>> </div> </div> </div>
<<set $Flags = { ...$Flags, sizemax: true, enbedroom: false, endispatch: false, enstorage: false, enbuild: false, enstudy: false, enbastion: false, entrain: false, miraelflag: false, catmaidflag: false, leahflag: false, butleflag: false, captainflag: false, griffinknightflag: false, scalemaidflag: false, foxmaidflag: false } >> <<set $cards = [] >> <<set $skillbag = { fireRateBoost: 3, gateHeal: 1, freeze: 1 }>> <<newinv $storage "storehouse">> <<newinv $blackmarket "blackmarket">> <<newinv $battlebag "battle">> <<pickup $storage "wood" $domain.initConfig.wood "food" $domain.initConfig.food "gold" $domain.initConfig.gold>> <<pickup $storage "staPotion-5" 5 "staPotion-1" 5>> <<set $times.currentWeather = setup.TimeSystem.getRandomWeather() >> <<run changeEffects(0, 1, "bastion")>> <<run changeEffects(0, 1, "bastion", 0, "wall_defense")>> <<run changeEffects(0, 1, "bastion", 1, "gate_defense")>> <<run changeEffects(0, 1, "bastion", 2, "tower_arrow")>> <<run changeEffects(0, 1, "warren")>> <<run changeEffects(0, 2, "warren", 0, "Dormitory")>> <<set $tech.enTraits = ["Diligent","Slacker"]>> <<set $manage.summon.difficulty = 0.5>> <<include "Dispatch_Data">>
<<widget InitGame>> <<run Engine.restart();>> <</widget>> <<widget "ObjectList">> <<for _key, _value range _args[0]>> <span class="item-name"> | <<= _key>>: </span><span class="item-value"><<= _value>></span> <</for>> <</widget>> <<widget "ToggleTags">> <<set _cssclass = "." + _args[0]>> <<set _cssid = "#" + _args[1]>> <<removeclass _cssclass 'active'>> <<addclass _cssid 'active'>> <</widget>> <<widget Unleashed>> <<pickup $storage "potentialPotion-1" 5>> <<pickup $storage "uncommonPotion-1" 5>> <</widget>> <<widget SkipTutorial>> <<run Msg.add('notice', setup.freeModeNotice, -1)>> <<set $domain.maps[1] = "Map_Vale02" >> <<pickup $storage "wood" 20 "food" 232 "gold" 2000>> <<set $battle.demonattackdeadline += 29 >> <<for _i range 29>> <<DailyUpdate>> <<run setup.TimeSystem.passOneDay();>> <</for>> <<NextDayEvent>> <<run newMarket()>> <<run $chars.dataMap.get("you").img = "atlas/unique/you_armor.png">> <<run $chars.dataMap.get("you").atlas.unique.cloth = "you_armor">> <<run $chars.dataMap.get('promaid').img = "characters/promaid/promaid02.webp">> <<run $chars.dataMap.get('captain').img = "characters/captain/captain02.webp">> <<run $chars.dataMap.get('butler').img = "characters/butler/butler02.png">> <<run $chars.dataMap.get('headmaid').img = "characters/headmaid/headmaid02.webp">> <<set $Flags = { ...$Flags, skiptutor: true, tutorial: 999, endintro: false, firstbirth: true, enbedroom: 2, endispatch: 2, enstorage: 2, enbuild: 2, enstudy: 2, enbastion: 2, entrain: 2, eninfuser: 2, enmeeting: 2, foxmaidflag: 9, scalemaidflag: 9, leahflag: 9, catmaidflag: 9, miraelflag: 9 } >> <<run $tech.enTrees.pushUnique("evolution");>> <<run $tech.enTrees.pushUnique("goddess");>> <<run $tech.enTrees.pushUnique("training");>> <<pickup $storage "Basic_Infuser_Core" 1>> <<run setup.TechTreeEngine.unlockNodeById("infuser1");>> <<run setup.TechTreeEngine.unlockNodeById("feline1", true);>> <<run setup.TechTreeEngine.unlockNodeById("canine1", true);>> <<run changeEffects(0, 1, "warren")>> <<run changeEffects(0, 1, "hawkridge_hamlet")>> <<run changeEffects(0, 1, "verdant_wilds")>> <<run changeEffects(0, 1, "quarry_mines")>> <<run changeEffects(0, 1, "bountiful_fields")>> <<AddMonster $chars.dataMap.get("catmaid")>> <<run $training.bedroomGirls.pushUnique("catmaid")>> <<run $chars.dataMap.get("catmaid").calcAttrInfo("bond.vl", 2, false)>> <<set $chars.dataMap.get("catmaid").alivedays = 30 >> <<set $chars.dataMap.get("catmaid").img = "atlas/unique/catmaid_maid.png" >> <<AddMonster $chars.dataMap.get("mirael")>> <<= $manage.ownrace.set("allureborn", 2) >> <<run $chars.dataMap.get("mirael").calcAttrInfo("bond.vl", 2, false)>> <<set $chars.dataMap.get("mirael").alivedays = 22 >> <<AddMonster $chars.dataMap.get("scalemaid")>> <<run $chars.dataMap.get("scalemaid").img = "atlas/unique/scalemaid_start.png">> <<set $chars.dataMap.get("scalemaid").alivedays = 10 >> <<AddMonster $chars.dataMap.get("foxmaid")>> <<set $chars.dataMap.get("foxmaid").alivedays = 5 >> <<QuestActive "main_ritual">> <<QuestActive "main_rebuild">> <<run EventBus.emit('time:morning')>> <<set $gamestatus.stage = 3 >> <</widget>> <<widget "SimpleUI">> <<if _args[1] == 90>> <<run Dialog.create("", "simplepop w90");>> <<run Dialog.wikiPassage(_args[0])>> <<run Dialog.open();>> <<elseif _args[1] == 100>> <<run Dialog.create("", "simplepop w100");>> <<run Dialog.wikiPassage(_args[0])>> <<run Dialog.open();>> <<elseif _args[1] == "fit">> <<run Dialog.create("", "simplepop fit");>> <<run Dialog.wikiPassage(_args[0])>> <<run Dialog.open();>> <<elseif _args[1] == "fullpop">> <<run Dialog.create("", "fullpop");>> <<run Dialog.wikiPassage(_args[0])>> <<run Dialog.open();>> <<else>> <<run Dialog.create("", "simplepop");>> <<run Dialog.wikiPassage(_args[0])>> <<run Dialog.open();>> <</if>> <</widget>> <<widget SimpleWiki>> <<run Dialog.create(_args[2], _args[1])>> <<run Dialog.wiki(_args[0])>> <<run Dialog.open();>> <</widget>> <<widget "ToHome">> <<run Dialog.create("to main menu","");>> <<run Dialog.wiki('Return to Main? <br>Unsaved progress will be lost.<br><div class="buttons-group-row"><<button " OK ">><<run Engine.restart();>><</button>><<button " No ">><<run Dialog.close();>><</button>></div>')>> <<run Dialog.open();>> <</widget>> <<widget "SetFlag">> <<set _Fnam = _args[0].toLowerCase()>> <<if ndef $Flags>> <<set $Flags = {}>> <</if>> <<if def _args[1]>> <<if _args[1] == false>> <<if def $Flags[_Fnam]>> <<run delete $Flags[_Fnam]>> <</if>> <<else>> <<set $Flags[_Fnam] = _args[1]>> <</if>> <<else>> <<set $Flags[_Fnam] = true>> <</if>> <</widget>> <<widget "csslink" container>> <<set _xtt = `_args[0]` >> <<set _btt = `<div class="csslink" @id="_args[1]">_xtt</div>` >> <<link `_btt`>>_contents<</link>> <</widget>> <<widget "cssbutton" container>> <<set _xtt = `_args[0]` >> <<set _btt = `<div class="cssbutton" @id="_args[1]">_xtt</div>` >> <<button `_btt`>>_contents<</button>> <</widget>> <<widget DisplayToggle>> <<set _key to setup.newID()>> <<set _keyid to "#"+_key>> <<set _onText to _args[1] ?? "ON">> <<do tag _key>> <<set _toggleclass = "toggleButton " + (_args[0] ? 'active' : '')>> <<link '<div @class="_toggleclass" @id="_key"><<= _args[0] ? _onText : "">></div>'>> <<toggleclass _keyid "active">> <<set _args[0] = _args[0] ? false : true >> <<redo _key>> <</link>> <</do>> <</widget>> <<widget ToggleSetting>> <<set _key to setup.newID()>> <<set _keyid to "#"+_key>> <<set _onText to _args[1] ?? "ON">> <<do tag _key>> <<set _oldvalue = Setting.getValue(_args[0]) >> <<set _toggleclass = "toggleButton " + (_oldvalue ? 'active' : '')>> <<link '<div @class="_toggleclass" @id="_key"><<= _oldvalue ? _onText : "">></div>'>> <<toggleclass _keyid "active">> <<set _newvalue = _oldvalue ? false : true >> <<run Setting.setValue(_args[0], _newvalue)>> <<redo _key>> <</link>> <</do>> <</widget>> <<widget "TutorList">> <<done>> <<run new Tutorial(_args[0]);>> <</done>> <</widget>> <<widget "includeplus">> <<set setup.currentView = _args[0]>> <<run UIBar.stow(true);>> <<switch _args[0]>> <<case "Girl" "Train" "Bedroom">> <<run setup.changeBG('/ui/bg/bedroom02.webp')>> <<case "Monsters">> <<run setup.changeBG('/ui/bg/dungeon01.webp')>> <<case "Meeting">> <<run setup.changeBG('/ui/bg/meetingroom01.webp')>> <<case "Dispatch">> <<run setup.changeBG('/ui/bg/qwall.webp')>> <<case "Summon">> <<run setup.changeBG('/ui/bg/temple01.webp')>> <<case "Storage">> <<run setup.changeBG('/ui/bg/storage01.webp')>> <<case "Building">> <<run setup.changeBG('/ui/bg/blueprint.webp')>> <<case "Bestiary" "Job">> <<run setup.changeBG('/ui/bg/darkpaper.webp')>> <<case "MainHall" "Battle" "TechTree" "Codex" "Market" "Slaver">> <<run setup.changeBG('COLOR:var(--bg-body)')>> <<default>> <<run setup.changeBG('COLOR:var(--bg-body)')>> <</switch>> <<switch _args[0]>> <<case "MainHall">> <<run AudioBus.playPlaylist('bgm_mainhall');>> <<case "Train">> <<run AudioBus.playPlaylist('bgm_girl');>> <<case "Battle">> <<run AudioBus.playPlaylist('bgm_heroic');>> <<case "Bedroom">> <<run AudioBus.playPlaylist('bgm_romance');>> <<case "Monsters">> <<run AudioBus.playPlaylist('bgm_joyous');>> <<default>> <</switch>> <<run setup.killTutorial();>> <<run EventBus.emit("passage:includeplus");>> <<include _args[0]>> <</widget>>
<div class="gene-help" style="max-width: 60rem; margin: 0 auto;"> <div class="module-bigtitle">Genetics Mechanics</div> <div class="flow-container"> <div class="flow-node active"> <div class="flow-icon"><span>XP</span></div> <div class="flow-text">Bodypart<br>Mastery</div> </div> <div class="flow-arrow">➜</div> <div class="flow-node"> <div class="flow-icon"><span>Mod</span></div> <div class="flow-text">Genetics<br>Potential</div> </div> <div class="flow-arrow">➜</div> <div class="flow-node"> <div class="flow-icon"><span>ΣPot</span></div> <div class="flow-text">Total<br>Potential</div> </div> <div class="flow-arrow">➜</div> <div class="flow-node active"> <div class="flow-icon"><span>Tier</span></div> <div class="flow-text">Stat<br>Tier</div> </div> <div class="flow-arrow">➜</div> <div class="flow-node"> <div class="flow-icon"><span>EXP</span></div> <div class="flow-text">Stat<br>Experience</div> </div> <div class="flow-arrow">➜</div> <div class="flow-node"> <div class="flow-icon"><span>Stat</span></div> <div class="flow-text">Stat<br>Value</div> </div> </div> <div class="section-panel" style="margin-bottom: 1.5rem;"> <div class="section-title"> <span>I. The Origin (Genetic Evolution)</span> <span style="font-size: 0.7rem; color: #666;">INTERNAL</span> </div> <div class="grid-3col"> <div class="gothic-card"> <div class="card-title"> Bodyparts Mastery <span class="card-tag">XP</span> </div> <div class="card-desc"> Accumulated experience for specific body parts. Caps result in permanent genetic evolution. </div> <div> <div class="flex-row-sb1" style="font-size: 0.75rem; color: #aaa;"> <span>Progress</span> <span>60%</span> </div> <div class="stat-bar-mini"> <div class="codex-stat-fill" style="width: 60%;"></div> </div> </div> <div class="source-row"> <span><i>💟</i>Sex Times</span> <span><i>🗝️</i>Training</span> <span><i>💋</i>Sexual Job</span> </div> </div> <div class="gothic-card"> <div class="card-title"> Bodyparts Genetic Potential <span class="card-tag">Mod</span> </div> <div class="card-desc"> Innate biological limit. Increases only when Bodyparts Mastery reaches 100%.<br> Bodyparts Genetic Base X Modifiers(Innate differences + Mastery) =Bodyparts Genetic Stat.(Can be inherited with mutation) </div> <div class="flex-row-sb1" style="align-items: center; margin-top: 1rem;"> <span style="font-size: 0.8rem; color: #777;">Current Value</span> <div class="value-big">STR +9, STA +3, DEX -2</div> </div> </div> <div class="gothic-card"> <div class="card-title"> Total Synthesis <span class="card-tag">SUM</span> </div> <div class="card-desc"> Aggregate score of all body parts. Dictates the entity's Gene Tier classification. </div> <div class="flex-row-sb1" style="align-items: center; margin-top: 1rem;"> <span style="font-size: 0.8rem; color: #777;">Aggregate</span> <div class="value-big">STR:75, STA:50, CHA:20, FER:35 ...</div> </div> </div> </div> </div> <div class="section-panel"> <div class="section-title"> <span>II. The Manifestation (Stat Upgrade)</span> <span style="font-size: 0.7rem; color: #666;">EXTERNAL</span> </div> <div class="grid-3col"> <div class="gothic-card" style="border-color: var(--accent-bronze);"> <div class="card-title"> Stat Tier <span class="card-tag" style="border-color: var(--accent-red); color: var(--accent-red);">CORE</span> </div> <div class="card-desc"> Upgrade stage. Higher tiers amplify XP gain from all sources.<br> Tier SS has an experience multiplier of 800%, while F has 100%. </div> <div class="flex-row-sb1" style="align-items: center; margin-top: 1rem;"> <span style="font-size: 0.8rem; color: #777;">STR Genetic Sum: 75 -> </span> <div class="item-value S">STR Tier: SS</div> </div> </div> <div class="gothic-card"> <div class="card-title"> Stat Experience <span class="card-tag">EXP</span> </div> <div class="card-desc"> Stat Experience gained via Battle, Labor and Rituals. Fills to raise Stat Value. </div> <div> <div class="flex-row-sb1" style="font-size: 0.75rem; color: #aaa;"> <span>To Next</span> <span>60%</span> </div> <div class="stat-bar-mini"> <div class="codex-stat-fill" style="width: 60%; background: var(--accent-bronze);"></div> </div> </div> <div class="source-row"> <span><i>🔮</i>Summon</span> <span><i>⚔️</i>Battle</span> <span><i>⚒️</i>Larbor Job</span> </div> </div> <div class="gothic-card" style="border-left-color: var(--accent-gold);"> <div class="card-title"> Stat Value <span class="card-tag" style="color: var(--accent-gold); border-color: var(--accent-gold);">Stat</span> </div> <div class="card-desc"> Visible power value. Determines Deployment eligibility and Resource Yield.<br> Individual stats, not inherited. </div> <div class="flex-row-sb1" style="align-items: center; margin-top: 1rem;"> <span style="font-size: 0.8rem; color: #777;">STR Stat</span> <div class="item-value B">15</div> </div> </div> </div> <div style="margin-top: 1rem; padding-top: 1rem; border-top: 1px solid #222;"> <ul class="module-ul"> <li><span style="color: var(--text-primary);">Deployment Req:</span> Minimum level required to dispatch unit on missions.</li> <li><span style="color: var(--text-primary);">Resource Yield:</span> Production output multiplier for base job.</li> <li><span style="color: var(--text-primary);">Harvest Yield:</span> The yield of harvest is related to the Stats.</li> <li>Tier determine the growth potential (EXP gain multiplier) for corresponding Stat.</li> <li>A monster girl's own genes, individual differences, and training result modifiers can all be inherited with some degree of mutation.</li> <li>A freshly summoned powerful monster girl will have high gene value but low base stats—yet she'll level up extremely quickly.</li> </ul> </div> </div> </div>
<<widget "GeneFullPanel">> <<set _character = _args[0]>> <<set _partKey = _args[1]>> <<set _data = setup.buildGeneData(_character, _partKey)>> <<if _data>> <div class="gene-full-panel"> <div class="gfp-header"> <div class="gfp-title-group"> <span class="gfp-part-name"><<= _data.base.partName>></span> <span class="gfp-type-name"><<= _data.base.type>></span> </div> <div class="gfp-tags"> <span class="gfp-tag origin">Origin: <<= _data.base.origin>></span> <span class="gfp-tag rank">Rank: <<= _data.base.rank>></span> </div> </div> <div class="gfp-desc"><<= _data.base.desc>></div> <<if _data.bonuses.length > 0>> <div class="gfp-bonuses"> <<for _bonus range _data.bonuses>> <span @class="'gfp-bonus-pill ' + (_bonus.isPositive ? 'pos' : 'neg')"> <<= _bonus.stat>> <<= _bonus.value>> </span> <</for>> </div> <</if>> <<if _data.base.isTraining && _data.training>> <div class="gfp-section-title">Development Status</div> <div class="gfp-training-grid"> <div class="gfp-stat-box" style="grid-column: span 2;"> <span class="gfp-stat-label">Awakening Level: <<= _data.training.level>></span> <div class="train-stat-bar" style="width: 60%; height: 0.8rem; background: #374151; border-radius:4px; overflow:hidden; position:relative;"> <<set _pct = Math.min(100, (_data.training.exp / _data.training.nextExp) * 100)>> <div class="train-stat-fill" style="background: #10b981;" @style="`width: ${_pct}%;`"></div> <span class="train-stat-value" style="font-size:0.65rem;"><<= _data.training.exp>> / <<= _data.training.nextExp>></span> </div> </div> <div class="gfp-stat-box gfp-rating-box"> <span class="gfp-stat-label">Overall Rating(WIP):</span> <span class="gfp-rating-text"> <<= _data.rating.rankText>> (<<= _data.rating.score>>) <<if _data.rating.cupSize>>[<<= _data.rating.cupSize>> Cup]<</if>> </span> </div> <div class="gfp-stat-box"> <span class="gfp-stat-label">Times Used:</span> <span class="gfp-stat-value"><<= _data.training.times>></span> </div> <div class="gfp-stat-box"> <span class="gfp-stat-label">First Time:</span> <<if _data.training.isVirgin && _data.base.partName === "pussy">> <span class="gfp-stat-value virgin">🌸 Virgin</span> <<else>> <span class="gfp-stat-value text-capitalize"><<= _data.training.firstPartner>></span> <</if>> </div> </div> <<if _data.dimensions.length > 0>> <div class="gfp-section-title">Physical Dimensions(WIP)</div> <div class="gfp-dimensions-grid"> <<for _dim range _data.dimensions>> <div class="gfp-dim-item"> <span class="gfp-dim-label"><<= _dim.label>></span> <span class="gfp-dim-val"><<= _dim.value>> <<= _dim.unit>></span> </div> <</for>> </div> <</if>> <</if>> <div class="gfp-footer"> <<HoverTip setup.geneModBrief>> <span>Innate Mod: <<= _data.modifiers.baseSizeMod>>%</span> <span>Inheritable Mod: <<= _data.modifiers.totalFactor>>%</span> <</HoverTip>> </div> </div> <<else>> <div class="gene-full-panel" style="opacity: 0.5;"> <div class="gfp-header"> <span class="gfp-part-name text-capitalize"><<= _partKey>></span> <span class="gfp-type-name">Not Found / Undeveloped</span> </div> </div> <</if>> <</widget>>
<<run AudioBus.playPlaylist('bgm_girl');>> <<set $chars.selected = null >> <<do tag "pregnancylist">> <div class="character-card-container"> <<for _charid range $training.bedroomGirls>> <<set _char = $chars.dataMap.get(_charid)>> <<capture _char>> <div class="character-card"> <div class="room-char-image"><<HeadImageByID _char.id>></div> <div class="character-overlay"> <div class="character-name">_char.name</div> <div class="character-role">❤️<<= _char.currentAPFloor>></div> <<if _char.preg > 0>> <div class="character-info"> <span>Pregnancy: <<= _char.preg>>days</span><span> Childbirth: <<= Math.clamp(270 - _char.preg, 1, 270)>>days</span> </div> <<else>> <</if>> <div class="buttons-group-row"> <<button "Status">> <<set $chars.selected = _char >> <<SimpleUI "Girl_Info" 90>> <</button>> <<HoverTip setup.notice_infusecost>> <<button "Infuser">> <<if $manage.energy.current >=500 && _char.currentAPFloor >= 1>> <<run _char.makePregnant()>> <<if Flag('miraelflag') == 5>> <<run Evm.flash('Event_Mirael_pregnancy')>> <<elseif Flag('catmaidflag') == 5 && _char.id == "catmaid">> <<run Evm.flash('Event_Stellafall_pregnancy')>> <<else>> <<if _char.preg>> <<set _cimgDir = setup.getInteractionCGPath(_char.id, "preg") >> <<run Dialog.create();>> <<run Dialog.wiki(`<<cimg _cimgDir>> <br>Blessed by the divine power of the Fertility Goddess and the mystic infusion of the Energy Infuser. The Infuser has hastened her pregnancy! <span> <<button "OK">><<run Dialog.close();>><</button>></span>`)>> <<run Dialog.open();>> <<else>> <<set _cimgDir = setup.getInteractionCGPath(_char.id, "bed") >> <<run Dialog.create();>> <<run Dialog.wiki('<<cimg _cimgDir>><br>Blessed by the divine power of the Fertility Goddess and the mystic infusion of the Energy Infuser, your efforts have borne fruit, and she is now with child. The Infuser has hastened her pregnancy! <span> <<button "OK">><<run Dialog.close();>><</button>></span>')>> <<run Dialog.open();>> <</if>> <</if>> <<set $manage.energy.current -= 500 >> <<run _char.useAP() >> <<run $manage.preg.infuserCount += 1>> <<run setup.pairOnce(_char, $chars.dataMap.get("you"), false)>> <<else>> <<run Msg.flash("This action requires 500 energy and 1 AP from the target.")>> <</if>> <<redo "pregnancylist topbar">> <</button>><</HoverTip>> <<if Flag('entrain') == 2>> <<button "Train">> <<set $training.trainID.to = _char.id>><<goto 'Train_UI'>><<run Dialog.close();>> <<redo "pregnancylist">> <</button>> <</if>> <<if _char.preg > 0>> <<button `langBank('feed')`>> <<SetFlag 'enblefeed'>> <<set $manage.feed.feedCp.charID = _char.id>> <<SimpleUI "Storage">> <</button>> <</if>> </div> </div> </div> <</capture>> <</for>> </div> <</do>>
<div class="fullInfo"> <<Infolist $chars.selected>> <<ShowPortrait $chars.selected>> <<set _withGirl = $chars.selected || $chars.dataMap.get("catmaid") >> <<TrainDataBox _withGirl>> </div>
<<widget GetBirth>> <<set _getbirth = $chars.dataMap.get(_args[0])>> <<if _getbirth.preg>> <<BirthMonsters _getbirth $chars.dataMap.get("you")>> <<set _getbirth.preg = 0 >> <<run $training.pregs.deleteAll(_args[0])>> <<run _getbirth.removeTrainState("pregnant");>> <<setPregCG _args[0]>> <<run EventBus.emit("char:birth", { charId: _args[0], fatherId: "you", day: State.variables.times.total });>> <<else>> <</if>> <</widget>> <<widget BirthMonsters>> <<set $cards = [] >> <<set _cardnum = $manage.cardSelect >> <<for _i range _cardnum>> <<Get5 _args[0] _args[1] _i "Birth">> <</for>> <<run Dialog.create("cards", "fullpop");>> <<run Dialog.wiki("<<CardShow _args[0] _args[1]>>") >> <<run Dialog.open()>> <<notify 2s>>[<span><<= _args[0].name>></span>] gave birth<</notify>> <</widget>>
<<widget "StatCard">> <<set _infolistRole = _args[0]>> <<set _attr = _args[1]>> <div class="statcard-box"> <div class="statcard-title"><<= setup.langbank[_attr][$lang]>> Stats</div> <div class="statcard-progress-wrap"> <progress style="line-height: 1; vertical-align: unset; width: 100%;" class="attribute-progress" @max="_infolistRole.upgradeXp(_attr)" @value="_infolistRole.currentExp(_attr)"></progress> <div class="statcard-progress-text"><span>Stat Exp progress</span><span><<= _infolistRole.currentExp(_attr)>> / <<= _infolistRole.upgradeXp(_attr)>></span></div> </div> <div class="statcard-data-section"> <div class="statcard-group-header">General</div> <div>Value</div><div class="statcard-data-value"><<= _infolistRole.getStatFinal(_attr)>></div> <div>Base</div><div class="statcard-data-value"><<= _infolistRole[_attr].lv>></div> <div>Add</div><div class="statcard-data-value"><<= _infolistRole[_attr].add>></div> <div>Mult</div><div class="statcard-data-value"><<= _infolistRole[_attr].rate * 100>>%</div> <div>True</div><div class="statcard-data-value"><<= _infolistRole[_attr].pure>></div> <div class="statcard-group-header">Growth</div> <div>Grade</div><div class="statcard-data-value"><<= _infolistRole.getRank(_attr)>></div> <div>Growth</div><div class="statcard-data-value"><<= _infolistRole[_attr].op>></div> <div>Exp Gain</div><div class="statcard-data-value"><<= `+${(_infolistRole.attrRate(_attr) * 100).toFixed(0)}%`>></div> </div> <div class="statcard-footer-section"> <div class="statcard-effect-text"><<= setup.langbank[_attr+'desc'][$lang]>></div> <div class="statcard-formula-box">Stat Value = <br> (Base + Add) × (1 + Mult) + True </div> </div> </div> <</widget>> <<widget AttrShow>> <div class="core-attr-box"> <<set _attrColor = `background: var(--tr-${_args[0].getRank(_args[1])});` >> <span @class="'core-attr-talent module-class-color'" @style=_attrColor><<= _args[0].getRank(_args[1])>></span> <div class="core-attr-progress"> <progress class="core-attr-bar" @max='_args[0].upgradeXp(_args[1])' @value='_args[0].currentExp(_args[1])'></progress> <div class="core-attr-content"> <span class="core-attr-name"><<= _args[1]>></span> <span class="core-attr-level"><<= _args[0][_args[1]].lv>></span> </div> </div> </div> <</widget>> <<widget RadarImageByChar>> <div class="radar-top-section" @id="'radar-' + _args[0].id"> <div class="radar-leftbox"> <div class="radar-container"> <svg viewBox="0 0 100 100"> <defs> <linearGradient id="orangeGradient" x1="0%" y1="0%" x2="100%" y2="100%"> <stop offset="0%" style="stop-color:#ffa500;stop-opacity:1" /> <stop offset="100%" style="stop-color:#cc7000;stop-opacity:1" /> </linearGradient> </defs> <polygon class="radar-background" points="50,0 93.3,25 93.3,75 50,100 6.7,75 6.7,25" /> <line x1="50" y1="50" x2="50" y2="0" /> <line x1="50" y1="50" x2="93.3" y2="25" /> <line x1="50" y1="50" x2="93.3" y2="75" /> <line x1="50" y1="50" x2="50" y2="100" /> <line x1="50" y1="50" x2="6.7" y2="75" /> <line x1="50" y1="50" x2="6.7" y2="25" /> <polygon id="data-polygon" class="radar-data" /> </svg> <div class="radar-labels"> <div class="radar-label radar-label-strength">STR</div> <div class="radar-label radar-label-dexterity">DEX</div> <div class="radar-label radar-label-fertility">FER</div> <div class="radar-label radar-label-charisma">CHA</div> <div class="radar-label radar-label-willpower">WIL</div> <div class="radar-label radar-label-stamina">STA</div> </div> <div class="radar-mode-indicator"></div> </div> </div> </div> <<done>> <<script TwineScript>> const args0 = _args[0]; const PI = Math.PI; const statKeys = [ { id: 'str', name: 'strength', angle: -PI / 2 }, { id: 'dex', name: 'dexterity', angle: PI / 3 - PI / 2 }, { id: 'fer', name: 'fertility', angle: 2 * PI / 3 - PI / 2 }, { id: 'cha', name: 'charisma', angle: PI - PI / 2 }, { id: 'wil', name: 'willpower', angle: 4 * PI / 3 - PI / 2 }, { id: 'sta', name: 'stamina', angle: 5 * PI / 3 - PI / 2 } ]; const tiers = []; const lvlTable = setup.geneTier.table; for (let i = 0; i < lvlTable.length; i++) { const t = lvlTable[i].tag; if (t && t !== 'None' && tiers.indexOf(t) === -1) { if (t === 'F' && tiers.indexOf('N') === -1 && i === 0) { continue; } tiers.push(t); } } let pivotIndex = tiers.indexOf('S'); if (pivotIndex === -1) { pivotIndex = Math.floor(tiers.length * 0.6); } const maxIndex = tiers.length - 1; const advancedTags = tiers.slice(pivotIndex + 1); const charTags = {}; let highLevelCount = 0; for (let i = 0; i < statKeys.length; i++) { const key = statKeys[i].id; let currentTag = args0.getRank(key); charTags[key] = currentTag; if (advancedTags.indexOf(currentTag) !== -1) { highLevelCount++; } } const isHighLevel = highLevelCount >= 2; const getRatio = function(tagStr) { let idx = tiers.indexOf(tagStr); if (idx === -1) idx = 0; if (!isHighLevel) { if (idx <= pivotIndex) { return pivotIndex > 0 ? (idx / pivotIndex) : 0; } else { return 1.1; } } else { if (idx <= pivotIndex) { return pivotIndex > 0 ? 0.4 * (idx / pivotIndex) : 0; } else { const diff = maxIndex - pivotIndex; if (diff > 0) { return 0.4 + 0.7 * ((idx - pivotIndex) / diff); } else { return 1.1; } } } }; const radius = 50; const centerX = 50; const centerY = 50; const points = []; for (let i = 0; i < statKeys.length; i++) { const stat = statKeys[i]; const tag = charTags[stat.id]; const ratio = getRatio(tag); const x = centerX + radius * Math.cos(stat.angle) * ratio; const y = centerY + radius * Math.sin(stat.angle) * ratio; points.push(x.toFixed(2) + ',' + y.toFixed(2)); } const radarCont = document.getElementById('radar-' + args0.id); const radar = radarCont.querySelector('.radar-container'); radar.style.setProperty('--background-color', isHighLevel ? '#ffd700' : '#d3d3d3'); radar.style.setProperty('--stat-fill-color', isHighLevel ? 'url(#orangeGradient)' : 'rgba(0, 128, 255, 0.3)'); radar.style.setProperty('--stat-stroke-color', isHighLevel ? '#cc7000' : '#0052cc'); radarCont.querySelector('#data-polygon').setAttribute('points', points.join(' ')); radarCont.querySelector('.radar-mode-indicator').textContent = isHighLevel ? 'Advanced' : 'Basic'; <</script>> <</done>> <</widget>>
<<widget "TechDesc">> <div id="confirmBox"> <h3></h3>_args[0].name <<if $tech.techNodes.includes(_args[0].id)>> <p>Unlocked:</p> <<else>> <p>Spend <span id="confirmCost">_args[0].cost _args[0].track points</span> </p> <<if _args[0].keyItems>><p><span id="confirmCost"><<ObjectList _args[0].keyItems>></span> </p><</if>> <p>to unlock:</p> <</if>> <p id="confirmName" style="font-size: 1rem;color: #4191c1;">_args[0].bonusDesc</p> </div> <</widget>> <<widget "NumToStars">> <div class="talent-stars"> <<set _atttostars = _args[0] >> <<set _atttostarsCSS = Math.ceil(_atttostars / 2) >> <<for _i range _atttostarsCSS>> <<if _atttostars >= (_i + 1) * 2>> <<set _addstar = "talent-star active">> <<else>> <<set _addstar = "talent-star">> <</if>> <div @class='_addstar'></div> <</for>> </div> <</widget>> <<widget FeedItem>> <<set _feedItemId = _args[1].split('-')[0] >> <<set _feedItemLv = _args[1].split('-')[1] || 1>> <<set _feedMon = getMonsterByID(_args[0])>> <<if _feedMon.effects[_feedItemId] && (setup.itemdata[_feedItemId].spammable !== true)>> <<run setup.Popup(L.duplicateWarning);>> <<else>> <<run _feedMon.feedItem(_feedItemId, _feedItemLv)>> <<drop $storage _args[1] 1>> <<set $manage.feed.count += 1 >> <<set $manage.feed.feedCp.itemIDLv = null >> <</if>> <<redo "summon charStatus">> <</widget>>
<div id="job-panel"> <div class="overview-panel"> <div class="originbar-header"> <div class="originbar-title">Assignments</div> </div> <div class="overview-stat-box"> <div style="font-size:12px; color:#aaa;">Active Jobs</div> <<do tag "job_lists">> <div class="overview-number"> <<set _jobCache = JobSystem.getPreview()>> <<= _jobCache.workers || 0>> </div> <</do>> </div> <div class="global-council"> <div class="manager-label" style="margin-bottom: 15px;">Inner Circle</div> <<for _roleId, _roleConfig range setup.globalRolesConfig>> <<capture _roleId, _roleConfig>> <<set _workerId = $globalRoles[_roleId]>> <<set _onClickCode = 'setup.openJobFilterDialog({ type: \'global\', roleId: \'' + _roleId + '\' })'>> <div class="global-slot-container"> <<if _workerId>> <<set _worker = getMonsterByID(_workerId)>> <<set _score = JobSystem.Math.getMatchScore(_worker, _roleConfig, true)>> <<set _tier = JobSystem.getTierClass(_score)>> <<if _workerId !== "you">><<set _hoverText = "Inner Circle is locked for now. They pulled you from the grave—their loyalty is absolute." >><<else>><<set _hoverText = "To survive, everyone must earn their keep.">><</if>> <<HoverTip _hoverText>> <div class="square-slot" style="width: 50px; height: 50px; border-color: #d4af37;"> <div class="slot-job-badge"><<print _roleConfig.icon>></div> <div style="width:100%; height:100%; overflow:hidden;"> <<HeadImageByID _worker.id>> </div> </div> <</HoverTip>> <div class="global-slot-info"> <span class="global-slot-name"><<print _roleConfig.name>></span> <span class="global-slot-worker"><<print _worker.name>> (<span class="<<print _tier>>"><<print _score>>%</span>)</span> </div> <<else>> <div class="square-slot empty" style="width: 50px; height: 50px;"> <div class="slot-job-badge" style="filter: grayscale(1);"><<print _roleConfig.icon>></div> <div class="empty-icon" style="font-size: 18px; margin: 0;">+</div> </div> <div class="global-slot-info"> <span class="global-slot-name" style="color: #666;"><<print _roleConfig.name>></span> <span class="global-slot-worker" style="color: #444;">Vacant</span> </div> <</if>> </div> <</capture>> <</for>> </div> <div class="modern-action-bar"> <div class="action-buttons"> <<button '<div class="btn-glow-gold">✨ Auto Assign</div>'>> <<set _res = JobSystem.autoAssign()>> <<run Msg.flash(_res.msg)>> <<redo "job_lists">> <</button>> <<button '<div class="btn-ghost-red">🗑️ Clear Jobs</div>'>> <<set _cleared = JobSystem.clearAllJobs()>> <<if _cleared > 0>> <<run Msg.flash("Dismissed " + _cleared + " workers.")>> <<redo "job_lists">> <<else>> <<run Msg.flash("No workers to dismiss.")>> <</if>> <</button>> </div> </div> </div> <div class="main-view"> <<do tag "job_lists">> <<for _wingId, _wing range $buildings>> <<set _wingTemplate = setup.bdwingdata[_wingId]>> <<if _wingTemplate>> <div class="wing-header"><<= _wingTemplate.name.en>></div> <</if>> <<for _zoneId, _zone range _wing>> <<set _zoneTemplate = setup.bdZoneData[_zoneId]>> <<if _zoneTemplate && _zone.lv > 0>> <div class="zone-strip" style="flex-direction: column;"> <div style="display: flex; flex: 1;"> <div class="zone-info"> <div class="zone-meta"> <div class="zone-name"> <<= _zoneTemplate.name.en>> <div style="font-size:14px; color:#888;">Lv <<= _zone.lv>></div> </div> </div> <div class="zone-manager-container"> <<if _zoneTemplate.jobs && _zone.workers>> <<for _slotIdx, _workerId range _zone.workers>> <<set _clickLeadCtx = {'type': 'building', 'wingId': _wingId, 'zoneId': _zoneId, 'unitKey': _zoneId, 'slotIdx': _slotIdx} >> <<capture _clickLeadCtx _wingId, _zoneId, _slotIdx, _workerId, _zoneTemplate>> <<if _workerId>> <<set _worker = getMonsterByID(_workerId)>> <<set _score = JobSystem.Math.getMatchScore(_worker, _zoneTemplate, true)>> <<set _tier = JobSystem.getTierClass(_score)>> <div class="square-slot slot-manager"> <div class="slot-job-badge"><<= _zoneTemplate.jobs.jobicon>></div> <div style="width: 100%; height: 100%; overflow: hidden;"> <<HeadImageByID _worker.id>> </div> <div class="slot-overlay-name"><<= _worker.name>></div> <div @class="'slot-match-score ' + _tier"><<= _score>>%</div> <<button '<div class="btn-unassign-overlay active">❌</div>'>><<run executeJobFire(_clickLeadCtx)>><<redo "job_lists topbar">><</button>> </div> <<else>> <<set _linkAssign = `<div class="square-slot slot-manager empty"> <div class="slot-job-badge" style="filter: grayscale(1); border-color: #666;"><<= _zoneTemplate.jobs.jobicon>></div> <div class="empty-icon">+</div> <div class="empty-text">Lead</div> </div>` >> <<link _linkAssign>><<run setup.openJobFilterDialog(_clickLeadCtx)>><</link>> <</if>> <</capture>> <</for>> <</if>> </div> </div> <div class="units-grid"> <<for _unitKey, _unit range _zone>> <<if String(_unitKey).match(/^\d+$/) && _unit.lv > 0>> <<set _unitTemplate = setup.bdUnitData[_unit.id]>> <<if _unitTemplate && _unitTemplate.jobs && _unit.workers>> <<for _slotIdx, _workerId range _unit.workers>> <<set _clickCtx = {'type': 'building', 'wingId': _wingId, 'zoneId': _zoneId, 'unitKey': _unitKey, 'slotIdx': _slotIdx} >> <<capture _clickCtx _wingId, _zoneId, _unitKey, _slotIdx, _workerId, _unitTemplate>> <<if _workerId>> <<set _worker = getMonsterByID(_workerId)>> <<set _score = JobSystem.Math.getMatchScore(_worker, _unitTemplate, false)>> <<set _tier = JobSystem.getTierClass(_score)>> <div class="square-slot"> <div class="slot-job-badge"><<print _unitTemplate.jobs.jobicon>></div> <div style="width: 100%; height: 100%; overflow: hidden;"> <<HeadImageByID _worker.id>> </div> <div class="slot-overlay-name"><<print _worker.name>></div> <div @class="'slot-match-score ' + _tier"><<= _score>>%</div> <<button '<div class="btn-unassign-overlay active">❌</div>'>><<run executeJobFire(_clickCtx)>><<redo "job_lists topbar">><</button>> </div> <<else>> <<set _linkAssign = `<div class="square-slot empty"> <div class="slot-job-badge" style="filter: grayscale(1); border-color: #666;"><<= _unitTemplate.jobs.jobicon>></div> <div class="empty-icon">+</div> <div class="empty-text"><<= _unitTemplate.name.en>></div> </div>`>> <<link _linkAssign>><<run setup.openJobFilterDialog(_clickCtx)>><</link>> <</if>> <</capture>> <</for>> <</if>> <</if>> <</for>> </div> </div> <div class="zone-footer"> <<set _preview = setup.getZoneYieldPreview(_wingId, _zoneId)>> <div class="footer-stat-block" style="align-items: end;" title="Amplifies both Building & Worker yields.Amplifies Base Resources Only."> <span class="footer-label">Leader Boost:</span> <span class="text-green"> <<set _boostPercent = Math.floor(_preview.boost * 100)>> <<set _boostSign = _boostPercent > 0 ? '+' : ''>> <<set _boostColor = _boostPercent >= 0 ? 'text-green' : 'text-red'>> <span @class="_boostColor" style="font-weight: bold; font-size: 16px;"> <<print _boostSign>><<print _boostPercent>>% </span> </span> </div> <div class="footer-stat-block"> <span class="footer-label" style="display: block;">Total Yield / Day:</span> <<if Object.keys(_preview.income).length > 0>> <div style="display: grid;grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));gap: 8px;grid-auto-flow: column;"> <<for _res, _finalAmt range _preview.income>> <div style="height: 40px;line-height: 1.2;background: rgba(0,0,0,0.3);padding: 4px 8px;border-radius: 4px;border-left: 3px solid #d4af37;"> <div style="font-weight: bold; font-size: 12px;"> <<print _res.charAt(0).toUpperCase() + _res.slice(1)>>: <span class="text-gold" style="font-size: 12px;">+<<print _finalAmt>></span> </div> <div style="font-size: 11px; color: #aaa; margin-top: 3px;"> <span title="Passive yield from building levels"> 🏭 Base: <<print _preview.rawBuilding[_res] || 0>> </span> | <span title="Active yield from assigned workers"> ⛏️ Jobs: <<print _preview.rawWorker[_res] || 0>> </span> </div> </div> <</for>> </div> <<else>> <span style="color:#666;" title="Specialized zone. Provides services, capacities, or stats rather than direct resources.">No resources generated.</span> <</if>> </div> </div> </div> <</if>> <</for>> <</for>> <</do>> </div> </div>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<includeplus "Job">> </div> <<include "UI_RightBar">> </div> </div>
<<widget "JobCharFilter">> <<set _ctx = _jobTarget>> <<set $chars.selected = null>> <<set _baseReqs = _ctx.isManager ? setup.jobGlobalReqs.manager : setup.jobGlobalReqs.worker>> <<set _specificReqs = (_ctx.template && _ctx.template.jobs && _ctx.template.jobs.reqs) ? _ctx.template.jobs.reqs : {}>> <<set _finalReqs = Object.assign({}, _baseReqs, _specificReqs)>> <<set _cond = new Quest({})>> <<set _cond.hasunique= true>> <<set _cond.reqprops= []>> <<set _cond.reqtraits= []>> <<for _path, _val range _finalReqs>> <<if _path === "level.lv">> <<set _cond.reqlevel = _val>> <<else>> <<run _cond.reqprops.push({ reqatt: _path, reqvalue: _val })>> <</if>> <</for>> <<set _filtergather = filterMonsterByCondition($chars.templeList, _cond).ids>> <div class="itemfilter-title">Job Filter</div> <div class="itemfilter"> <div class="itemfilter-list"> <<MapList _filtergather>> </div> <div class="itemfilter-info"> <div class="job-req-card"> <h3><<= _ctx.isManager ? "Leader - " : "">> <<= _ctx.template.name.en || _ctx.template.name>> - Requirements</h3> <div class="job-req-content"> • Status: Idle (Not currently working)<br> <<if _ctx.type === 'global'>> • Global Directing Role<br> • Focus: <span class="text-gold"><<= _ctx.template.reqDesc>></span><br> <</if>> <<for _path, _val range _finalReqs>> • <span class="text-gold"><<= setup.formatReqName(_path)>></span> >= <<= _val>><br> <</for>> </div> </div> <<do tag "selected">> <div> <<if $chars.selected>> <<Infocard $chars.selected>> <</if>> </div> <</do>> <div class="job-preview-card"> <<do tag "selected">> <<if $chars.selected>> <h3 class="gold-text" style="margin: 15px 0 5px 0;"><<print $chars.selected.name>></h3> <div style="font-size: 13px; color: #aaa; margin-bottom: 15px;"> Lv.<<print $chars.selected.level.lv>> | <<print $chars.selected.race>> </div> <<set _score = JobSystem.Math.getMatchScore($chars.selected, _ctx.template, _ctx.isManager)>> <<set _tier = JobSystem.getTierClass(_score)>> <div style="padding: 10px; background: rgba(0,0,0,0.8); border: 1px solid #333; border-radius: 5px; margin-bottom: 15px;"> <span style="font-size: 12px; color: #888;">Competency Match:</span><br> <span @class="_tier" style="font-size: 24px; font-weight: bold;"><<= _score>>%</span> </div> <div style="padding: 10px; background: rgba(0,0,0,0.5); border: 1px dashed #555; font-size: 13px;"> <<if _ctx.type === 'global'>> Oversees global operations. Effect active across the entire domain. <<else>> <<print setup.previewJobIncomeText($chars.selected.id, _ctx.wingId, _ctx.zoneId, _ctx.unitKey)>> <</if>> </div> <<else>> <i class="fas fa-user-tie" style="font-size: 50px; color:#333; margin-bottom:15px;"></i> <p style="color:#666; font-size: 14px;">Select an idle subject to view their compatibility and projection.</p> <</if>> <</do>> </div> <div class="job-btn-group"> <<button '<div class="job-btn assign">Assign</div>'>> <<if !$chars.selected>> <<run Msg.flash(L.selecttarget)>> <<else>> <<run executeJobFire(_ctx);>> <<run executeJobAssignment($chars.selected, _ctx);>> <<set $chars.selected = null>> <<redo _args[1]>><<redo "total topbar job_lists">> <<run setup.Popup.close()>> <</if>> <</button>> <<set _hasWorker = (_ctx.type === 'global') ? ($globalRoles[_ctx.roleId] !== null) : (_ctx.template && _ctx.slotIdx != null && $buildings[_ctx.wingId][_ctx.zoneId][_ctx.unitKey] && $buildings[_ctx.wingId][_ctx.zoneId][_ctx.unitKey].workers[_ctx.slotIdx] !== null)>> <<if _hasWorker>> <<button '<div class="job-btn unassign">Relieve</div>'>> <<run executeJobFire(_ctx);>> <<redo _args[1]>><<redo "total topbar job_lists">> <<run setup.Popup.close()>> <</button>> <</if>> </div> </div> </div> <</widget>>
<<script>> (() => { l10nStrings.textAbort = 'Abort'; l10nStrings.textAborting = 'Aborting'; l10nStrings.textCancel = 'Cancel'; l10nStrings.textClear = 'Clear'; l10nStrings.textClose = 'Close'; l10nStrings.textDelete = 'Delete'; l10nStrings.textExport = 'Export'; l10nStrings.textIdentity = 'game'; l10nStrings.textImport = 'Import'; l10nStrings.textLoad = 'Load'; l10nStrings.textOff = 'Off'; l10nStrings.textOk = 'OK'; l10nStrings.textOn = 'On'; l10nStrings.textSave = 'Save'; l10nStrings.textTurn = 'Turn'; l10nStrings.errorNonexistentPassage = 'the passage "{passage}" does not exist'; l10nStrings.warningNoStorage = 'All usable storage APIs are missing. Possible causes are a disabled third-party cookie setting, which also affects Web Storage, or a private browsing mode.'; l10nStrings.warningNoWebStorage = 'The Web Storage API is missing, so this {textIdentity} is running in a degraded mode. You may be able to continue, however, some parts may not work properly.'; l10nStrings.warningDegraded = 'Some capabilities required to support this {textIdentity} are missing, so it is running in a degraded mode. You may be able to continue, however, some parts may not work properly.'; l10nStrings.warningNoSaves = 'Some capabilities required to support saves are missing, so saves have been disabled for this session.'; l10nStrings.saveErrorDisallowed = 'Saving is currently disallowed.'; l10nStrings.saveErrorDecodeFail = 'unable to decode save, likely due to corruption'; l10nStrings.saveErrorDiskLoadFail = 'failed to load save file from disk'; l10nStrings.saveErrorIdMismatch = 'save is from the wrong {textIdentity}'; l10nStrings.saveErrorInvalidData = 'save is missing required data, likely due to corruption'; l10nStrings.saveErrorNonexistent = 'save does not exist'; l10nStrings.uiBarLabelToggle = 'Toggle the UI bar'; l10nStrings.uiBarLabelBackward = 'Go backward within the {textIdentity} history'; l10nStrings.uiBarLabelForward = 'Go forward within the {textIdentity} history'; l10nStrings.uiBarLabelJumpto = 'Jump to a specific point within the {textIdentity} history'; l10nStrings.alertTitle = 'Notices'; l10nStrings.restartTitle = 'Restart'; l10nStrings.restartMesgPrompt = 'All unsaved progress will be lost. Are you sure that you want to restart?'; l10nStrings.continueTitle = 'Continue'; l10nStrings.savesTitle = 'Saves'; l10nStrings.savesTitle = ''; l10nStrings.savesHeaderBrowser = 'In Browser'; l10nStrings.savesHeaderDisk = 'On Disk'; l10nStrings.savesLabelBrowserClear = 'Clear all browser saves'; l10nStrings.savesLabelBrowserExport = 'Export browser saves to bundle'; l10nStrings.savesLabelBrowserImport = 'Import browser saves from bundle'; l10nStrings.savesLabelDiskLoad = 'Load from disk'; l10nStrings.savesLabelDiskSave = 'Save to disk'; l10nStrings.savesTextBrowserAuto = 'Auto'; l10nStrings.savesTextBrowserSlot = 'Slot'; l10nStrings.savesTextNoDate = 'unknown date'; l10nStrings.settingsTitle = 'Settings'; l10nStrings.settingsTextReset = 'Reset to Defaults'; l10nStrings.errorViewTitle = 'Error'; l10nStrings.errorViewLabelToggle = 'Toggle the error view'; l10nStrings.debugBarLabelToggle = 'Toggle the debug bar'; l10nStrings.debugBarLabelViewsToggle = 'Toggle the debug views'; l10nStrings.debugBarLabelWatchAdd = 'Add a new watch'; l10nStrings.debugBarLabelWatchAll = 'Watch all'; l10nStrings.debugBarLabelWatchClear = 'Clear all watches'; l10nStrings.debugBarLabelWatchDelete = 'Delete this watch'; l10nStrings.debugBarLabelWatchPlaceholder = 'variable name'; l10nStrings.debugBarLabelPassagePlaceholder = 'passage name'; l10nStrings.debugBarLabelPassagePlay = 'Play passage'; l10nStrings.debugBarLabelWatchToggle = 'Toggle the watch panel'; l10nStrings.debugBarMesgNoWatches = 'No watches set'; l10nStrings.debugBarTextAdd = 'Add'; l10nStrings.debugBarTextPassage = 'Passage'; l10nStrings.debugBarTextViews = 'Views'; l10nStrings.debugBarTextWatch = 'Watch'; l10nStrings.macroBackText = 'Back'; l10nStrings.macroReturnText = 'Return'; l10nStrings.autoloadTitle = 'Autoload'; l10nStrings.autoloadMesgPrompt = 'An autosave exists. Load it now or go to the start?'; l10nStrings.autoloadTextCancel = 'Go to start'; l10nStrings.autoloadTextOk = 'Load autosave'; l10nStrings.jumptoTitle = 'Jump To'; l10nStrings.jumptoMesgUnavailable = 'No jump points currently available\u2026'; l10nStrings.shareTitle = 'Share'; })(); <</script>>
<div class="hallcon"> <div class="mainhall-room" id="planarrift"> <span id="hallrift"><<link [img[setup.ImagePath+'ui/icon/rift01.png'][Summon_UI]]>> <<if Flag('tutorial') == 4>><<SetFlag 'tutorial' 5>><</if>> <</link>></span> <div class="mainhall-btn-name" style="color: var(--text-white);text-shadow: 2px 2px 2px #000000;"><<if Flag('tutorial') >= 2>><<= "🌌"+L.planarrift>><<else>><<= "A strange rift">><</if>></div> </div> <<if Flag('tutorial') >= 3>> <div class="mainhall-room" id="mainhall-warren"> <<link "<<showframe 'door128' 'Warren' 7>>" "Monsters_UI">><</link>> <div class="mainhall-btn-name"><<if Flag('tutorial') >= 4>><<= "😸"+ L.Warren>><<else>><<= L.chambers>><</if>></div> </div> <</if>> <<if Flag('enbuild') == 2>> <div class="mainhall-room" id="Building"> <<link "<<showframe 'icon64' 'upgrade' 5>>" "Building_UI">><</link>> <div class="mainhall-btn-name"><<= "Building">></div> </div> <</if>> <<if Flag('enstudy') == 2>> <div class="mainhall-room" id="Study"> <<link "<<showframe 'icon64' 'evolution' 5>>" "TechTree_UI">><</link>> <div class="mainhall-btn-name"><<= "Study">></div> </div> <</if>> <<if Flag('tutorial') >= 6>> <div class="mainhall-room" id="mainhall-bedroom"> <<link "<<showframe 'icon64' 'bedroom' 5>>" "Bedroom_UI">><</link>> <div class="mainhall-btn-name"><<= "🌙"+ L.bedroom>></div> </div> <</if>> <<if Flag('endispatch') == 2 || Flag('tutorial') >= 7>> <div class="mainhall-room" id="mainhall-dispatch"> <<link "<<showframe 'door128' 'dispatch' 5>>" "Dispatch_UI">><</link>> <div class="mainhall-btn-name"><<= L.Dispatch>></div> </div> <</if>> <<if Flag('tutorial') >= 8>> <div class="mainhall-room" id="Storage"> <<link "<<showframe 'icon64' 'storage' 5>>" "Storage_UI">><</link>> <div class="mainhall-btn-name"><<= L.storage>></div> </div> <</if>> <<if Flag('enmeeting') == 2>> <div class="mainhall-room" id="MeetingRoom"> <<link "<<showframe 'icon64' 'meeting' 5>>" "Meeting_UI">><</link>> <div class="mainhall-btn-name"><<= "👑"+"Meeting">></div> </div> <</if>> </div> <<done>> <<if Flag('tutorial') == 1>> <<append "#hallrift">> <<set _tutorl = [ { el: '#hallrift', text: 'A strange rift hovers in the main hall, curiously approach to explore.<br>Click glowing button to next step.' } ] >> <<TutorList _tutorl>> <</append>> <</if>> <<if Flag('tutorial') == 3>> <<append "#mainhall-warren">> <<set _tutorl = [ { el: '#mainhall-warren .sprite-wrapper', text: "Where to find the right person? ...Wait, a noise from that chamber. Better take a look." } ] >> <<TutorList _tutorl>> <</append>> <</if>> <<if Flag('tutorial') == 4>> <<append "#hallrift">> <<set _tutorl = [ { el: '#hallrift', text: "Finally, all set! ...Why is this catgirl even more excited than me? Off to the summoning ritual!" } ] >> <<TutorList _tutorl>> <</append>> <</if>> <<if Flag('tutorial') == 6>> <<append "#mainhall-bedroom">> <<set _tutorl = [ { el: '#mainhall-bedroom', text: "When Energy or Stamina runs out, rest in the bedroom to start a new day."} ] >> <<TutorList _tutorl>> <</append>> <</if>> <<if Flag('tutorial') == 7>> <<append "#mainhall-dispatch">> <<set _tutorl = [ { el: '#mainhall-dispatch', text: L.DispatchNotice1 + "<br>" + L.sourceincome } ] >> <<TutorList _tutorl>> <</append>> <</if>> <<if Flag('tutorial') == 9>> <<append "#mapbtn">> <<set _tutorl = [ { el: '#mapbtn', text: 'Click the Map.' } ] >> <<TutorList _tutorl>> <</append>> <<SetFlag 'tutorial' 10>> <</if>> <<if Flag('tutorial') == 12>> <<append "#MeetingRoom">> <<set _tutorl = [ { el: '#MeetingRoom', text: "This will be the hub for gathering intel and making decisions." } ] >> <<TutorList _tutorl>> <</append>> <<SetFlag 'tutorial' 13>> <</if>> <</done>> <<if Flag('catmaidflag') == 1>> <<AddMonster $chars.dataMap.get("catmaid")>> <<run $chars.dataMap.get("catmaid").calcAttrInfo("bond.vl", 1)>> <<run Msg.notify("[<span>Stellafall</span>] Joined")>> <<run Msg.notify("Unlock [<span>Hollow Warren</span>] Zone")>> <<SetFlag 'catmaidflag' 2>> <</if>> <<if Flag('catmaidflag') == 3>> <<QuestActive "main_ritual">> <<run Msg.add('notice', "The Planar Rift is currently the only mysterious force you can harness, so you’d better study how to make the most of it. <br>Activate quest: Tame the Planar Rift’s Sultry Secrets. <br>Please click the Quest in the Right Bar.")>> <<run Msg.notify("Unlock [<span>Maps</span>] Function")>> <<run Msg.notify("Unlock [<span>Quest</span>] Function")>> <<run Msg.notify("Unlock [<span>Baron Bedroom</span>] Zone")>> <<run Msg.notify("Activate [<span>Tame Ritual</span>] Quest")>> <<SetFlag 'catmaidflag' 4>> <</if>> <<if Flag('endispatch') == 1>> <<run Msg.notify("Unlock [<span>Dispatch Board</span>] Zone")>> <<run Msg.notify("Unlock [<span>Storage Chamber</span>] Zone")>> <<run Msg.notify("Unlock [<span>Feed</span>] Function")>> <<run Msg.notify("Unlock [<span>Harvest</span>] Function")>> <<SetFlag 'endispatch' 2>> <<SetFlag 'enstorage' 2>> <</if>> <<if Flag('griffinknightflag') == 1>> <<set $domain.wartaxdeadline = 90 >> <<set $battle.demonattackdeadline = 3 >> <<SetFlag 'wartaxs'>> <<SetFlag 'showwartaxs' false>> <<SetFlag 'entry_Characters_griffinknight' 0>> <<QuestActive "main_defend">> <<run Msg.add('notice', "The Griffin Knights bring urgent news: demons will attack in 3 days. Prepare your defenses now. <br>Activate quest: Defend Hawkridge Vale Against the Dark Legion. <br>Please click the map icon in the top-right corner and check the Quest in the pop-up window.")>> <<run Msg.notify("Activate [<span>Pass Defend</span>] Quest")>> <<run Msg.notify("Unlock [<span>Bastion</span>] Zone")>> <<run Msg.notify("Dark Legion attack in [<span>3 Days</span>]")>> <<SetFlag 'griffinknightflag' 2>> <</if>> <<if Flag('scalemaidflag') == 1>> <<AddMonster $chars.dataMap.get("scalemaid")>> <<run $chars.dataMap.get("scalemaid").img = "atlas/unique/scalemaid_start.png">> <<notify 2s>>[<span>Sariya Thornscale</span>] Joined<</notify>> <<SetFlag 'scalemaidflag' 2>> <</if>> <<if Flag('foxmaidflag') == 1>> <<run Msg.add('notice', "The Black Market becomes a permanent fixture, changing from appearing every Thursday to restocking goods every Thursday, with rare alchemical potions now available for sale.")>> <<SetFlag 'foxmaidflag' 2>> <</if>> <<if Flag('miraelflag') == 4>> <<run Msg.add('notice', setup.notice_eninfused)>> <<SetFlag 'miraelflag' 5>> <</if>>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<includeplus "MainHall">> </div> <<include "UI_RightBar">> </div> </div>
<<done>> <<script>> $('.mapsUI #barony_map .map-content').css('background-image', 'url("images/ui/map/Barony01.webp")'); <</script>> <<replace "#brmapName">>Barony Map<</replace>> <<replace "#brmapDesc">>The demon-held lands are shrouded in uncertainty.<</replace>><</done>> <div id="vale" class="maps-item" data-left="1160" data-top="0" data-width="128" data-height="144" data-bg-color="url('images/ui/map/vale.png') no-repeat center / contain" data-map="vale_map"><span>Hawkridge Vale</span></div> <<do tag "settlement">> <<if setup.mapPoint_isShow("feline_duskwood")>> <div id="feline_tribe" class="maps-item maps-tribe" style="position: relative;left: 4rem;top: 14rem;"> <<link '<<showframe "speciesTribe" "feline_tribe" 6>>'>> <<run setup.Popup("<<include 'Map_FelineTribe'>>")>> <</link>> <div>Feline Tribe</div> <div class="mappoint-popcount"><<= setup.mapPoint_showinfo("feline_duskwood", "pop")>></div> <div class="mappoint-recruitcount"><<= setup.mapPoint_showinfo("feline_duskwood", "recruit")>></div> </div> <</if>> <<if setup.mapPoint_isShow("sirenia_tidebreak")>> <div id="sirenia_tribe" class="maps-item maps-tribe" style="position: relative;right: 5rem;top: 9rem;"> <<link '<<showframe "speciesTribe" "sirenia_tribe" 6>>'>> <<run setup.Popup("<<include 'Map_SireniaTribe'>>")>> <</link>> <div>Sirenia Tribe</div> <div class="mappoint-popcount"><<= setup.mapPoint_showinfo("sirenia_tidebreak", "pop")>></div> <div class="mappoint-recruitcount"><<= setup.mapPoint_showinfo("sirenia_tidebreak", "recruit")>></div> </div> <</if>> <</do>>
<<done>> <<script>> $('.mapsUI #world_map .map-content').css('background-image', 'url("images/ui/map/world02.webp")'); <</script>> <<replace "#wdmapName">>World Map(WIP)<</replace>> <<replace "#wdmapDesc">>Communications are down due to the demon invasion. The world is left in the dark.<</replace>> <</done>> <div id="hawkridge_barony" class="maps-item maps-tribe" style="line-height: 1.3;font-size: 10px;position: absolute;left: 48rem;top: 42rem;" data-map="barony_map"> <<link 'Hawkridge<br> Barony'>><<removeclass "#world_map" "active">><<addclass "#barony_map" "active">><</link>> </div>
<<set _pointData = $mapPoint["feline_duskwood"] >> <<do tag "settlement">> <div class="fpopup-container" @style="'--fpopup-bg: url(\'' + _pointData.bg + '\');'"> <div class="fpopup-inner-rim"></div> <div class="fpopup-bg-overlay"></div> <div class="fpopup-content"> <div class="fpopup-npc-pane"> <img @src="_pointData.npc" class="fpopup-npc-img" alt="Feline Chief" /> </div> <div class="fpopup-interactive-pane"> <div class="fpopup-header"> <h2 class="fpopup-title">_pointData.name</h2> <p class="fpopup-subtitle">_pointData.desc</p> <<if _pointData.alliance>> <span class="fpopup-alliance-badge">◆ Alliance Active</span> <<else>> <span class="fpopup-alliance-badge" style="color:#e68787; background:rgba(140,30,30,0.15); border-color:rgba(180,40,40,0.4)">◆ No Alliance</span> <</if>> </div> <div class="fpopup-status-panel"> <div class="fpopup-status-item"> <span>Settlement Level:</span> <<if _pointData.alliance>> <span class="fpopup-status-value">Lv _pointData.level</span> <<else>> <span class="fpopup-status-value">Lv ???</span> <</if>> </div> <div class="fpopup-status-item"> <span>Settled Population:</span> <<if _pointData.alliance>> <span class="fpopup-status-value">_pointData.pop</span> <<else>> <span class="fpopup-status-value">???</span> <</if>> </div> <div class="fpopup-status-item"> <span>Available Recruits:</span> <<if _pointData.alliance>> <span class="fpopup-status-value fpopup-glow-green">_pointData.recruit</span> <<else>> <span class="fpopup-status-value" style="color:#8c8270">???</span> <</if>> </div> </div> <div class="fpopup-actions-list"> <<if _pointData.alliance>> <div class="fpopup-btn-wrapper"> <<if _pointData.pop > 0>> <<link "Relocate Population (_pointData.pop)">> <<run PopSystem.autoSettle("feline", _pointData.pop);>> <<run Msg.flash(`${_pointData.pop} Population have migrated to your barony!`)>> <<set _pointData.pop = 0 >> <<redo "settlement">> <</link>> <<else>> <div class="fpopup-btn-wrapper fpopup-btn-disabled"> <a href="javascript:void(0)">Relocate Population</a> </div> <</if>> </div> <<else>> <div class="fpopup-btn-wrapper fpopup-btn-disabled"> <a href="javascript:void(0)">Manage Settlement</a> </div> <</if>> <<if _pointData.alliance>> <<if _pointData.recruit > 0>> <div class="fpopup-btn-wrapper fpopup-btn-glowing"> <<link "Recruit Felines (_pointData.recruit)">> <<run setup.Popup('<<set _examRace = "feline" >><<include "Battle_ExamineCaptive">>', { buttons: [ { text: "Ignore", cancel: () => setup.Popup.close() } ] })>> <<set _pointData.recruit -= 1 >> <<redo "settlement">> <</link>> </div> <<else>> <div class="fpopup-btn-wrapper fpopup-btn-disabled"> <a href="javascript:void(0)">No Recruits Available</a> </div> <</if>> <<else>> <div class="fpopup-btn-wrapper fpopup-btn-disabled"> <a href="javascript:void(0)">Recruit Felines</a> </div> <</if>> <<if _pointData.alliance && 2===3>> <div class="fpopup-btn-wrapper fpopup-btn-glowing"> <<link "Search Duskwood">> <</link>> </div> <<else>> <div class="fpopup-btn-wrapper fpopup-btn-disabled"> <a href="javascript:void(0)">Search Duskwood</a> </div> <</if>> <<if _pointData.alliance>> <div class="fpopup-btn-wrapper"> <<link "Speak with Chief">> <<include "Event_FelineChief_Chitchat">> <</link>> </div> <<else>> <div class="fpopup-btn-wrapper fpopup-btn-disabled"> <a href="javascript:void(0)">Speak with Chief</a> </div> <</if>> <div class="fpopup-btn-wrapper" style="margin-top: 5px;"> <<link "Return">> <<run Dialog.close()>> <<run setup.Popup.close()>> <</link>> </div> </div> </div> </div> </div> <</do>>
<<set _pointData = $mapPoint["sirenia_tidebreak"] >> <<do tag "settlement">> <div class="fpopup-container" @style="'--fpopup-bg: url(\'' + _pointData.bg + '\');'"> <div class="fpopup-inner-rim"></div> <div class="fpopup-bg-overlay"></div> <div class="fpopup-content"> <div class="fpopup-npc-pane"> <img @src="_pointData.npc" class="fpopup-npc-img" alt="Feline Chief" /> </div> <div class="fpopup-interactive-pane"> <div class="fpopup-header"> <h2 class="fpopup-title">_pointData.name</h2> <p class="fpopup-subtitle">_pointData.desc</p> <<if _pointData.alliance>> <span class="fpopup-alliance-badge">◆ Alliance Active</span> <<else>> <span class="fpopup-alliance-badge" style="color:#e68787; background:rgba(140,30,30,0.15); border-color:rgba(180,40,40,0.4)">◆ No Alliance</span> <</if>> </div> <div class="fpopup-status-panel"> <div class="fpopup-status-item"> <span>Settlement Level:</span> <<if _pointData.alliance>> <span class="fpopup-status-value">Lv _pointData.level</span> <<else>> <span class="fpopup-status-value">Lv ???</span> <</if>> </div> <div class="fpopup-status-item"> <span>Settled Population:</span> <<if _pointData.alliance>> <span class="fpopup-status-value">_pointData.pop</span> <<else>> <span class="fpopup-status-value">???</span> <</if>> </div> <div class="fpopup-status-item"> <span>Available Recruits:</span> <<if _pointData.alliance>> <span class="fpopup-status-value fpopup-glow-green">_pointData.recruit</span> <<else>> <span class="fpopup-status-value" style="color:#8c8270">???</span> <</if>> </div> </div> <div class="fpopup-actions-list"> <<if _pointData.alliance>> <div class="fpopup-btn-wrapper"> <<if _pointData.pop > 0>> <<link "Relocate Population (_pointData.pop)">> <<run PopSystem.autoSettle("sirenia", _pointData.pop);>> <<run Msg.flash(`${_pointData.pop} Population have migrated to your barony!`)>> <<set _pointData.pop = 0 >> <<redo "settlement">> <</link>> <<else>> <div class="fpopup-btn-wrapper fpopup-btn-disabled"> <a href="javascript:void(0)">Relocate Population</a> </div> <</if>> </div> <<else>> <div class="fpopup-btn-wrapper fpopup-btn-disabled"> <a href="javascript:void(0)">Manage Settlement</a> </div> <</if>> <<if _pointData.alliance>> <<if _pointData.recruit > 0>> <div class="fpopup-btn-wrapper fpopup-btn-glowing"> <<link "Recruit Felines (_pointData.recruit)">> <<run setup.Popup('<<set _examRace = "feline" >><<include "Battle_ExamineCaptive">>', { buttons: [ { text: "Ignore", cancel: () => setup.Popup.close() } ] })>> <<set _pointData.recruit -= 1 >> <<redo "settlement">> <</link>> </div> <<else>> <div class="fpopup-btn-wrapper fpopup-btn-disabled"> <a href="javascript:void(0)">No Recruits Available</a> </div> <</if>> <<else>> <div class="fpopup-btn-wrapper fpopup-btn-disabled"> <a href="javascript:void(0)">Recruit Sirenia</a> </div> <</if>> <<if _pointData.alliance && 2===3>> <div class="fpopup-btn-wrapper fpopup-btn-glowing"> <<link "Search Duskwood">> <</link>> </div> <<else>> <div class="fpopup-btn-wrapper fpopup-btn-disabled"> <a href="javascript:void(0)">Search Tidebreak</a> </div> <</if>> <<if _pointData.alliance>> <div class="fpopup-btn-wrapper"> <<link "Speak with Chief">> <<include "Event_FelineChief_Chitchat">> <</link>> </div> <<else>> <div class="fpopup-btn-wrapper fpopup-btn-disabled"> <a href="javascript:void(0)">Speak with Chief</a> </div> <</if>> <div class="fpopup-btn-wrapper" style="margin-top: 5px;"> <<link "Return">> <<run Dialog.close()>> <<run setup.Popup.close()>> <</link>> </div> </div> </div> </div> </div> <</do>>
<<done>> <<script>> $('.mapsUI #temple_map .map-content').css('background-image', 'url("images/ui/map/templeMap01.webp")'); <</script>> <<replace "#tpmapName">>Temple Interior Map<</replace>> <<replace "#tpmapDesc">>The mysterious temple interior! <</replace>> <</done>> <div class="maps-item" data-left="225" data-top="482" data-width="144" data-height="144" data-bg-color="url('images/ui/map/rift.png') no-repeat center / contain" data-passage="Summon_UI"><span>Planar Rift</span></div> <<if Flag('endispatch') == 2>><div class="maps-item" data-left="16" data-top="335" data-width="144" data-height="144" data-bg-color="url('images/ui/map/dispatch.png') no-repeat center / contain" data-passage="Dispatch_UI"><span>Dispatch Board</span></div><</if>> <<if Flag('enmeeting') == 2>><div class="maps-item" data-left="16" data-top="545" data-width="144" data-height="144" data-bg-color="url('images/ui/map/meeting.png') no-repeat center / contain" data-passage="Meeting_UI"><span>Meeting Room</span></div><</if>> <<if Flag('enstorage') == 2>><div class="maps-item" data-left="192" data-top="193" data-width="144" data-height="144" data-bg-color="url('images/ui/map/storage.png') no-repeat center / contain" data-passage="Storage_UI"><span>Storage Chamber</span></div><</if>> <<if Flag('enbuild') == 2>><div class="maps-item" id="buildbtn" data-left="352" data-top="62" data-width="144" data-height="144" data-bg-color="url('images/ui/map/build.png') no-repeat center / contain" data-passage="Building_UI"><span>Building Workshop</span></div><</if>> <div class="maps-item" data-left="342" data-top="688" data-width="144" data-height="144" data-bg-color="url('images/ui/map/bedroom.png') no-repeat center / contain" data-passage="Bedroom_UI"><span>Baron Bedroom</span></div> <<if Flag('enstudy') == 2>><div class="maps-item" data-left="608" data-top="687" data-width="144" data-height="144" data-bg-color="url('images/ui/map/study.png') no-repeat center / contain" data-passage="TechTree_UI"><span>Monsters Study</span></div><</if>> <div class="maps-item" data-left="784" data-top="350" data-width="673" data-height="322" data-bg-color="url('images/ui/map/quarters.webp') no-repeat center / contain" data-passage="Monsters_UI"><span>Hollow Warren</span></div> <div class="maps-item" data-left="264" data-top="354" data-width="64" data-height="64" data-bg-color="url('images/ui/map/goddess.png') no-repeat center / contain"></div>
<<done>> <<script>> $('.mapsUI #vale_map .map-content').css('background-image', 'url("images/ui/map/Vale01.png")'); <</script>> <<replace "#vlmapName">>Hawkridge Vale<</replace>> <<replace "#vlmapDesc">>The Grange and its once-vibrant pastures lie shattered, reduced to rubble and ash. At the valley’s heart, a massive crater dominates the landscape. Amid the ruins of the manor, the debris has shifted, revealing the hidden entrance to a lost, ancient temple. <</replace>> <</done>> <<if $times.weekday == 2>><div class="maps-item" data-left="310" data-top="260" data-width="70" data-height="70" data-bg-color="rgba(255, 0, 179, 0.5)" data-passage="Slaver_UI"><<= setup.langbank.Slaver[$lang]>></div><</if>> <<if $times.weekday == 4>><div class="maps-item" data-left="310" data-top="515" data-width="70" data-height="70" data-bg-color="rgba(0, 49, 209, 0.5)" data-passage="Market_UI"><<= setup.langbank.market[$lang]>></div><</if>> <div id="temple" class="maps-item" data-left="1510" data-top="375" data-width="85" data-height="100" data-bg-color="rgba(190, 190, 190, 0.4)" data-passage="MainHall_UI">Lost Temple Entrance</div>
<<done>> <<script TwineScript>> $('.mapsUI #vale_map .map-content').css('background-image', 'url("images/ui/map/Vale02.webp")'); <</script>> <<replace "#vlmapName">>Hawkridge Vale<</replace>> <<replace "#vlmapDesc">>Once the sacred pastures of House Wilderen, steeped in ancient lore. Since the fall of Hawkridge, survivors have reclaimed these ruins—a final, defiant scar against the demonic tide.<</replace>> <</done>> <<if Flag('enbastion') == 2>><div class="maps-item" data-left="240" data-top="360" data-width="192" data-height="192" data-bg-color="url('images/ui/map/bastion.png') no-repeat center / contain" data-passage="Battle_UI"><span>Bastion</span> <<if $times.total >= 25>><div class="maphead">[img['images/characters/captain/head.png']]</div><</if>> </div><</if>> <div class="maps-item" data-left="550" data-top="650" data-width="192" data-height="192" data-bg-color="url('images/ui/map/wilds.png') no-repeat center / contain" data-passage="Building_UI"><span>Verdant Wilds</span> <<if $times.total >= 25>><div class="maphead">[img['images/characters/headmaid/head.png']]</div><</if>> </div> <<if Flag('entrade') == 2>> <div class="maps-item" data-left="290" data-top="570" data-width="192" data-height="192" data-bg-color="url('images/ui/map/trade.png') no-repeat center / contain" data-passage="Market_UI"><span>Trade Zone</span> <<if $times.total >= 25>><div class="maphead">[img['images/characters/foxmaid/head.png']]</div><</if>> </div> <<else>> <<if $times.weekday == 4 || $times.total >= 25>><div class="maps-item" data-left="290" data-top="580" data-width="128" data-height="128" data-bg-color="url('images/ui/map/market.png') no-repeat center / contain" data-passage="Market_UI"><span><<= langBank("market")>></span> <<if $times.total >= 25>><div class="maphead">[img['images/characters/foxmaid/head.png']]</div><</if>> </div><</if>> <</if>> <<if Flag('enexpedition') === 2>> <div class="maps-item" data-left="290" data-top="150" data-width="192" data-height="192" data-bg-color="url('images/ui/map/expedition.png') no-repeat center / contain" data-passage="Market_UI"><span>Expedition Camp</span> <<if 23 >= 25>><div class="maphead">[img['images/characters/foxmaid/head.png']]</div><</if>> </div> <<if $times.weekday == 2>><div class="maps-item" data-left="490" data-top="50" data-width="128" data-height="128" data-bg-color="url('images/ui/map/slaver.png') no-repeat center / contain" data-passage="Slaver_UI"><span><<= langBank("Slaver")>></span></div><</if>> <<else>> <<if $times.weekday == 2>><div class="maps-item" data-left="290" data-top="200" data-width="128" data-height="128" data-bg-color="url('images/ui/map/slaver.png') no-repeat center / contain" data-passage="Slaver_UI"><span><<= langBank("Slaver")>></span></div><</if>> <</if>> <div class="maps-item" data-left="1025" data-top="530" data-width="192" data-height="192" data-bg-color="url('images/ui/map/hamlet.png') no-repeat center / contain" data-passage="Building_UI"><span>Hawkridge Hamlet</span> <<if $times.total >= 25>><div class="maphead">[img['images/characters/promaid/head.png']]</div><</if>> </div> <div class="maps-item" data-left="800" data-top="40" data-width="192" data-height="192" data-bg-color="url('images/ui/map/mines.png') no-repeat center / contain" data-passage="Building_UI"><span>Rockvein Mines</span> <<if $times.total >= 25>><div class="maphead">[img['images/characters/butler/head.png']]</div><</if>> </div> <<if $buildingEffects.slots.bountiful_fields > 0>><div class="maps-item" data-left="1030" data-top="170" data-width="192" data-height="192" data-bg-color="url('images/ui/map/fields.png') no-repeat center / contain" data-passage="Building_UI"><span>Bountiful Fields</span></div><</if>> <div id="temple" class="maps-item" data-left="1510" data-top="405" data-width="85" data-height="100" data-bg-color="rgba(190, 190, 190, 0.4)" data-map="temple_map">Lost Temple Entrance <<if $times.total >= 25>><div class="maphead">[img['images/characters/lord/head.png']]</div><</if>> </div> <div class="maps-item" data-left="800" data-top="330" data-width="192" data-height="192" data-bg-color="url('images/ui/map/lake.png') no-repeat center / contain" data-passage=""><span>Dawnveil Lake</span></div>
<<script>> $(document).ready(function() { function scaleMarkers() { $('.map-container').each(function() { const mapId = $(this).attr('id'); const $mapContent = $(this).find('.map-content'); const $markers = $mapContent.find('.maps-item'); $markers.each(function() { const $marker = $(this); const left = parseFloat($marker.data('left')); const top = parseFloat($marker.data('top')); const width = parseFloat($marker.data('width')); const height = parseFloat($marker.data('height')); const bgColor = $marker.data('bg-color') || 'rgba(255, 215, 0, 0)'; $marker.css({ position: 'absolute', left: left + 'px', top: top + 'px', width: width + 'px', height: height + 'px', background: bgColor }); }); }); } $('.tab').click(function() { const mapId = $(this).data('map'); if (mapId == "barony_map") {AudioBus.playPlaylist('bgm_ruins');} else {AudioBus.playPlaylist('bgm_mainhall');} ; $('.map-container').removeClass('active'); $('#' + mapId).addClass('active'); scaleMarkers(); }); $('div.maps-item:not([id="vale"], [id="temple"])').click(function() { Dialog.close(); }); $('#vale').click(function() { const mapId = "vale_map"; $('.map-container').removeClass('active'); $('#' + mapId).addClass('active'); scaleMarkers(); }); $('#temple').click(function() { const mapId = "temple_map"; $('.map-container').removeClass('active'); $('#' + mapId).addClass('active'); scaleMarkers(); }); $('#temple').click(function() { const mapId = "temple_map"; $('.map-container').removeClass('active'); $('#' + mapId).addClass('active'); scaleMarkers(); }); $(window).resize(scaleMarkers); scaleMarkers(); }); <</script>> <div class="mapsUI"> <div id="barony_map" class="map-container"> <div class="tabs"> <div class="tab" data-map="temple_map">🗺️Lost Temple</div> <div class="tab" data-map="vale_map">🗺️Hawkridge Vale</div> <div class="tab active" data-map="barony_map">🗺️Barony Map</div> <div class="tab" data-map="world_map">🗺️World Map</div> </div> <div class="map-content"> <<include $domain.maps[2]>> </div> <div class="map-info"> <h3><span id="brmapName"></span></h3> <p><span id="brmapDesc"></span></p> </div> </div> <div id="vale_map" class="map-container"> <div class="tabs"> <div class="tab" data-map="temple_map">🗺️Lost Temple</div> <div class="tab active" data-map="vale_map">🗺️Hawkridge Vale</div> <div class="tab" data-map="barony_map">🗺️Barony Map</div> <div class="tab" data-map="world_map">🗺️World Map</div> </div> <div class="map-content"> <<include $domain.maps[1]>> </div> <div class="map-info"> <h3><span id="vlmapName"></span></h3> <p><span id="vlmapDesc"></span></p> </div> </div> <div id="temple_map" class="map-container active"> <div class="tabs"> <div class="tab active" data-map="temple_map">🗺️Lost Temple</div> <div class="tab" data-map="vale_map">🗺️Hawkridge Vale</div> <div class="tab" data-map="barony_map">🗺️Barony Map</div> <div class="tab" data-map="world_map">🗺️World Map</div> </div> <div class="map-content"> <<include $domain.maps[0]>> </div> <div class="map-info"> <h3><span id="tpmapName"></span></h3> <p><span id="tpmapDesc"></span></p> </div> </div> <div id="world_map" class="map-container"> <div class="tabs"> <div class="tab" data-map="temple_map">🗺️Lost Temple</div> <div class="tab" data-map="vale_map">🗺️Hawkridge Vale</div> <div class="tab" data-map="barony_map">🗺️Barony Map</div> <div class="tab active" data-map="world_map">🗺️World Map(WIP)</div> </div> <div class="map-content"> <<include $domain.maps[3]>> </div> <div class="map-info"> <h3><span id="wdmapName"></span></h3> <p><span id="wdmapDesc"></span></p> </div> </div> <<done>> <<if Flag('tutorial') == 10>> <<append "#buildbtn">> <<set _tutorl = [ { el: '#buildbtn', text: 'Click on the Building Workshop.' } ] >> <<TutorList _tutorl>> <</append>> <<SetFlag 'tutorial' 11>> <</if>> <</done>>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<includeplus "MapsPopup">> </div> <<include "UI_RightBar">> </div> </div>
<div class="blackmarket-panel"> <div class="trading-pane buyplus"> <div class="trade-totalbox"> <span class="item-illuminated-text"><b><<= L.yourinventory>></b></span> <span style="background: brown;"><<HoverTip 'A sell all for Sundries.'>><<button "Sell Sundries">> <<for _item, _amount range $storage.table>> <<if getiteminfo(_item,"type") == "sundries">> <<set _price = Math.round(getiteminfo(_item,"price"))>> <<pickup $storage "gold" _price*_amount>> <<drop $storage _item _amount >> <</if>> <</for>> <<redo "sell_buy">> <</button>><</HoverTip>></span> </div> <<do tag "sell_buy">> <div class="trading-grid"> <<if !_sellnum>> <<set _sellnum = [] >> <<set _buynum = [] >> <</if>> <<for _item, _amount range $storage.table>> <<if _item != "gold" && _item != "gold-1" && getiteminfo(_item,"type") != "special">> <<set _price = Math.round(getiteminfo(_item,"price") * $market.priceRate.sell)>> <<capture _item _amount _price>> <div class="item-card"> <div class="item-img"><<HoverTip '<<= getiteminfo(_item,"desc")>>'>><<ItemPicShow _item 3.5>><</HoverTip>> </div> <div class="trading-iteminfo"> <div style="display: flex; align-items: center; gap: 1rem;"><div class="item-name"><<= getiteminfo(_item,"name")>></div><<NumToStars `getiteminfo(_item,"lv")`>></div> <div class="trading-itemstat"> <div class="item-stat"> <span><<= "🏷️ "+ _price +"g">></span> </div> <div class="item-qty"><<= "Stock: "+ _amount>></div> </div> </div> <div class="trade-buttons"> <<if !_sellnum[_item]>><<set _sellnum[_item] = 1 >><</if>> <<do tag _item>><<numberbox "_sellnum[_item]" _sellnum[_item]>><</do>> <div class="market-all"><<button "All">><<set _sellnum[_item] = _amount >><<redo _item>><</button>></div> <<button "<<= L.sell>>">> <<if _sellnum[_item] < 0>><<set _sellnum[_item] = 0 >><</if>> <<if _sellnum[_item] > _amount>><<set _sellnum[_item] = _amount >><</if>> <<drop $storage _item _sellnum[_item] >> <<if _sellnum[_item] > 0>><<run Msg.notify(`Sold: ${getiteminfo(_item,"name")} x${_sellnum[_item]}`)>><</if>> <<pickup $storage "gold" _price*_sellnum[_item]>> <<redo "sell_buy">> <</button>> </div> <div> </div> </div> <div class="trading-hr"><hr></div> <</capture>> <</if>> <</for>> </div> <</do>> </div> <div class="trading-pane"> <div class="trade-totalbox"> <span class="item-illuminated-text"><b><<= L.blackmarket>></b></span> </div> <<do tag "sell_buy">> <div class="trading-grid"> <<for _item, _amount range $blackmarket.table>> <<set _price = Math.round(getiteminfo(_item,"price") * $market.priceRate.buy)>> <<capture _item _amount _price>> <div class="item-card"> <div class="item-img"> <<HoverTip '<<= getiteminfo(_item,"desc")>>'>><<ItemPicShow _item 3.5>><</HoverTip>> </div> <div class="trading-iteminfo"> <div style="display: flex; align-items: center; gap: 1rem;"> <div class="item-name"> <<= getiteminfo(_item,"name")>> </div> <<NumToStars `getiteminfo(_item,"lv")`>> </div> <div class="trading-itemstat"> <div class="item-stat"> <span><<= "🏷️ "+ _price +"g">></span> </div> <div class="item-qty"><<= "Stock: "+ _amount>></div> </div> </div> <div class="trade-buttons"> <<if !_buynum[_item]>><<set _buynum[_item] = 1 >><</if>> <<do tag _item>><<numberbox "_buynum[_item]" _buynum[_item]>><</do>> <div class="market-all"><<button "All">><<set _buynum[_item] = _amount >><<redo _item>><</button>></div> <<button "<<= L.buy>>">> <<if _buynum[_item] < 0>><<set _buynum[_item] = 0 >><</if>> <<if _buynum[_item] > _amount>><<set _buynum[_item] = _amount >><</if>> <<if $storage.count('gold') >= _price*_buynum[_item]>> <<transfer $blackmarket $storage _item _buynum[_item]>> <<drop $storage "gold" _price*_buynum[_item]>> <<if _buynum[_item] > 0>><<run Msg.notify(`Purchased: ${getiteminfo(_item,"name")} x${_buynum[_item]}`)>><</if>> <<redo "sell_buy">> <<else>> <<run Msg.flash(setup.langbank.moregold[$lang])>> <</if>> <</button>> </div> </div> <div class="trading-hr"><hr></div> <</capture>> <</for>> </div> <</do>> </div> <<if $times.total >= 25>><div class="marketcg"><<link "<<cimg 'images/atlas/unique/foxmaid_start.dat'>>">><<notify 3s>>Don’t even think about haggling, our clan’s survival depends on trade.<</notify>><</link>></div><</if>> </div> <<set _FacHelp = "CodexEntry_Building_BlackMarket">>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<includeplus "Market">> </div> <<include "UI_RightBar">> </div> </div>
<<timed 1s>> <<script>> let currentPosition = ''; let speechBubbleTimeout; const candidates = {}; window.showSpeechBubble = function(event, message) { if (speechBubbleTimeout) { clearTimeout(speechBubbleTimeout); } const bubble = document.getElementById('speechBubble'); const bubbleText = document.getElementById('bubbleText'); if (!bubble || !bubbleText) return; const container = document.getElementById('game-app-container'); if (container && bubble.parentElement !== container) { container.appendChild(bubble); } bubbleText.textContent = message; bubble.style.display = 'block'; const target = event.currentTarget; const rect = target.getBoundingClientRect(); let scale = 1, offsetX = 0, offsetY = 0; if (container) { const cRect = container.getBoundingClientRect(); scale = cRect.width / 1920; offsetX = cRect.left; offsetY = cRect.top; } const virtualLeft = (rect.left - offsetX) / scale; const virtualTop = (rect.top - offsetY) / scale; const targetVirtualWidth = rect.width / scale; const finalLeft = virtualLeft + (targetVirtualWidth / 2) - (bubble.offsetWidth / 4); bubble.style.left = `${finalLeft}px`; bubble.style.top = `${virtualTop - bubble.offsetHeight - 10}px`; speechBubbleTimeout = setTimeout(() => { bubble.style.display = 'none'; }, 2000); }; <</script>> <</timed>> <div class="meeting-panel"> <div class="left-panel"> <div class="originbar-header"> <div class="originbar-title" style="">Meeting</div> </div> <div class="map-controls"> <button class="meeting-map-btn" onclick='$.wiki(`<<replace "#meeting-map-overlay">><<include "Job">><</replace>>`)'>Assignments</button> <button class="meeting-map-btn" onclick='$.wiki(`<<replace "#meeting-map-overlay">><<include "TechTree">><</replace>>`)'>TechTree</button> <button class="meeting-map-btn" onclick='$.wiki(`<<replace "#meeting-map-overlay">><<include "DomainReport">><</replace>>`)'>Report</button> <button class="meeting-map-btn" onclick='$.wiki(`<<replace "#meeting-map-overlay">><<include "Quest">><</replace>>`)'>Quests</button> <button class="meeting-map-btn" onclick='$.wiki(`<<replace "#meeting-map-overlay">><<include "PopInfo">><</replace>>`)'>Population</button> <button class="meeting-map-btn" onclick='$.wiki(`<<replace "#meeting-map-overlay">><<include "Bedroom_MorningNews">><</replace>>`)'>News</button> </div> </div> <div class="map-container"> <div class="map-meeting"> <div id="meeting-map-overlay"></div> </div> </div> <div class="right-panel"> <<EventTabs>> </div> <div class="management-panel"> <div class="management-scroll"> <<set _CharSay = [ "Why do I feel like I’m back crunching numbers at the office... Still a corporate drone even after crossing worlds!", "Is Leah’s plan even legit? Teaming up with monster girls... I can barely handle a PowerPoint!", "This temple’s ledger is messier than my company’s financial reports...", "Demons hunting nobles? All I want is to clock out and grab a coffee...", "Why do I feel like I’m back at my old desk from before I got zapped here...?" ].random() >> <div class="manager-item" @data-say="_CharSay" onclick="showSpeechBubble(event, this.dataset.say)"> <div class="manager-avatar orange">[img[setup.ImagePath + $chars.dataMap.get("you").head]]</div> <div class="manager-name"><<= $chars.dataMap.get("you").name>></div> <div class="manager-title">Baron Wilderen</div> </div> <<set _CharSay = [ "The temple’s magical barrier is stable, my lord. No need to worry...", "Erin’s skills are improving; the wounded are recovering faster than expected.", "The Goddess of Fertility’s guidance is mysterious, but your mission is no small feat...", "The townsfolk need hope. Plans to rebuild Hawkridge Town are in place." ].random() >> <div class="manager-item" @data-say="_CharSay" onclick="showSpeechBubble(event, this.dataset.say)"> <div class="manager-avatar blue">[img[setup.ImagePath + $chars.dataMap.get('headmaid').head]]</div> <div class="manager-name"><<= $chars.dataMap.get('headmaid').name>></div> <div class="manager-title">Head Maid</div> </div> <<set _CharSay = [ "Damn those demons! Next time, I’ll smash them to bits myself!", "Guard training can’t slack, my lord! The mountain outposts need reinforcing!", "I should’ve held the line of town. Never again!", "Leah’s plan is weird as hell, but if it beats demons, I’m in!" ].random() >> <div class="manager-item" @data-say="_CharSay" onclick="showSpeechBubble(event, this.dataset.say)"> <div class="manager-avatar blue">[img[setup.ImagePath + $chars.dataMap.get('captain').head]]</div> <div class="manager-name"><<= $chars.dataMap.get('captain').name>></div> <div class="manager-title">Captain of Guard</div> </div> <<set _CharSay = [ "The glory of House Wilderen endures, even in the darkest of times...", "The inventory is complete, my lord. Hawkridge Vale is quite stable.", "The family’s warhorse secrets... If we rebuild the pasture, glory will return." ].random() >> <div class="manager-item" @data-say="_CharSay" onclick="showSpeechBubble(event, this.dataset.say)"> <div class="manager-avatar blue">[img[setup.ImagePath + $chars.dataMap.get('butler').head]]</div> <div class="manager-name"><<= $chars.dataMap.get('butler').name>></div> <div class="manager-title">Steward</div> </div> <<set _CharSay = [ "I... I’ll do my best! I won’t hold everyone back...", "The herb mix for the wounded... I’ll check it again, just in case...", "Does my lord really... not remember me?", "The temple statue... it feels like it’s watching me..." ].random() >> <div class="manager-item" @data-say="_CharSay" onclick="showSpeechBubble(event, this.dataset.say)"> <div class="manager-avatar blue">[img[setup.ImagePath + $chars.dataMap.get('promaid').head]]</div> <div class="manager-name"><<= $chars.dataMap.get('promaid').name>></div> <div class="manager-title">Maid Medic</div> </div> <<set _CharSay = [ "For now, the others aren’t sold on letting monster girls join the territory’s council meetings.", "No monster girls in the meeting? Are medieval folks’ brains this rigid? That’s species discrimination!", "Monster girls can’t join the table? This meeting’s as exclusive as my old company’s boardroom!", "Not even catgirls in the council? What, scared they’ll claw up the table?", "Monster girls aren’t qualified? Please, I can’t even manage an Excel sheet, let alone who joins the meeting!" ].random() >> <div class="manager-item" @data-say="_CharSay" onclick="showSpeechBubble(event, this.dataset.say)"> <div class="manager-avatar blue">?</div> <div class="manager-name">+</div> <div class="manager-title">?</div> </div> </div> </div> </div> <div id="speechBubble" class="meeting-speech-bubble"> <div class="speech-bubble-text" id="bubbleText"></div> </div>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<includeplus "Meeting">> </div> <<include "UI_RightBar">> </div> </div>
<div class="dark-fantasy-panel" style="text-align: center; padding: 15px;"> <h2 style="color: #c5a059; margin: 0; text-transform: uppercase; letter-spacing: 1px;"> 🏆 The Grand Tapestry </h2> <p style="color: #a89f91; margin: 5px 0 0 0; font-size: 0.9em;"> A testament to your conquests, depravities, and ultimate endurance. </p> <div style="margin-top: 10px; font-size: 1.1em;"> Soul Essence Available: <span class="soul-gem-text">💎 <<= Meta.gems>></span> </div> </div> <div style="margin-top: 16px; display: flex; justify-content: space-between; align-items: flex-end; border-bottom: 1px solid #4a252a; padding-bottom: 5px; margin-bottom: 15px;"> <h3 style="margin: 0; color: #c5a059;">Achievement Wall</h3></div> <div class="ach-gallery"> <<for _id, _ach range setup.AchievementsList>> <<set _isUnlocked to Meta.hasAch(_id)>> <div @class="'ach-card ' + (_isUnlocked ? 'unlocked' : 'locked')"> <div class="ach-icon">_ach.icon</div> <div class="ach-info"> <h4>_ach.name</h4> <p>_ach.desc</p> </div> <<if _isUnlocked>> <div class="ach-reward soul-gem-text">💎 +<<= _ach.rewardGems>></div> <<else>> <div class="ach-reward" style="color: #555;">🔒 Locked</div> <</if>> </div> <</for>> </div> <div style="background: rgba(139, 0, 0, 0.1); border: 1px solid #4a252a; padding: 15px; margin: 20px 0; border-radius: 4px;"> <h3 style="margin: 0 0 10px 0; color: #c5a059; font-size: 1.1em; border-bottom: 1px dashed #4a252a; padding-bottom: 5px;"> 🔮 Active Soul Imprints (Perks) </h3> <<set _totalSpent = 0>> <<set _hasActive = false>> <div style="display: flex; flex-wrap: wrap; gap: 10px;"> <<for _perkId, _lv range Meta.d.perks>> <<if _lv > 0>> <<set _hasActive = true>> <<set _db = setup.MetaPerksDB[_perkId] || setup.MetaPerksDB_back[_perkId]>> <<if _db>> <<set _totalSpent += (_db.cost * _lv)>> <div style="background: #160a0c; border: 1px solid #8b0000; padding: 5px 10px; border-radius: 3px; font-size: 0.9em;"> <span style="color: #e0d3af;">_db.name</span> <span style="color: #ffd700; font-weight: bold; margin-left: 5px;">Lv._lv</span> </div> <</if>> <</if>> <</for>> <<if !_hasActive>> <span style="color: #777; font-style: italic;">No active imprints... Your soul is a blank slate.</span> <</if>> </div> <div style="margin-top: 10px; text-align: right; font-size: 0.9em; color: #a89f91;"> Total Essence Infused: <span class="soul-gem-text">💎 <<= _totalSpent>></span> </div> </div> <div style="margin-top: 16px; display: flex; justify-content: space-between; align-items: flex-end; border-bottom: 1px solid #4a252a; padding-bottom: 5px; margin-bottom: 15px;"> <h3 style="margin: 0; color: #c5a059;">Bestiary Progress</h3></div> <div class="ach-gallery"> Discovered: <<= Object.keys(Meta.d.bestiarySync.speciesSave).length>> / Total: <<= Object.keys(setup.speciesDB).length>> </div>
<<widget "InheritData">> <<run setup.MetaManager.injectGlobalToState($bestiary)>> <<run bestiaryManager.rehydrateBonuses()>> <<set _pocketsLv to (setup.MetaManager.data.perks["deep_pockets"] || 0)>> <<if _pocketsLv > 0>> <<set $domain.initConfig.gold += (_pocketsLv * 200)>> <</if>> <<set _incubusLv to (setup.MetaManager.data.perks["incubus_blood"] || 0)>> <<if _incubusLv > 0>> <<set $manage.energy.base += (_incubusLv * 100)>> <<set $manage.energy.current += (_incubusLv * 100)>> <</if>> <<set _tongueLv to (setup.MetaManager.data.perks["golden_tongue"] || 0)>> <<if _tongueLv > 0>> <<set $market.priceRate.buy -= (_tongueLv * 0.05)>> <<set $slave.refreshBonus -= (_tongueLv * 0.05)>> <</if>> <<set _dominatorsLv to (setup.MetaManager.data.perks["dominators_aura"] || 0)>> <<if _dominatorsLv > 0>> <<set $inherited.train.tame += (_dominatorsLv * 10)>> <</if>> <<set _goddessLv to (setup.MetaManager.data.perks["goddesss_favor"] || 0)>> <<if _goddessLv > 0>> <<set $tech.techPoint.goddess += (_goddessLv * 50)>> <</if>> <<set _reservesLv to (setup.MetaManager.data.perks["endless_reserves"] || 0)>> <<if _reservesLv > 0>> <<set $chars.dataMap.get("you").stamina.strbonus += (_reservesLv * 10)>> <</if>> <</widget>> <<widget "AstralArchive">> <<do tag "AstralArchive">> <div class="dark-fantasy-panel"> <h2 style="color: #00f2ea; text-align: center; text-transform: uppercase; letter-spacing: 2px;"> 🏛️ Astral Archive </h2> <p style="text-align: center; color: #a89f91; margin-top: 10px;"> Time in the valley is but a canvas. Condense your current knowledge <br> and genetic memories to reshape your next awakening. <br> <span style="color:#ffd700;">Every new beginning is a chance to sculpt the perfect evolution.</span> </p> <div class="bestiary-notice"> <span style="font-size: 1.2em; vertical-align: middle;">📖</span> <strong>Knowledge Endures:</strong> Bestiary etched into your soul. The monsters you've discovered across all timelines will remember your touch in the next. </div> <div style="text-align: center; margin-top: 20px; font-size: 1.1em;"> Soul Essence Available: <span class="soul-gem-text">💎 <<= Meta.gems>></span> </div> </div> <div style="display: flex; justify-content: space-between; align-items: flex-end; border-bottom: 1px solid #4a252a; padding-bottom: 5px; margin-bottom: 15px;"> <h3 style="margin: 0; color: #c5a059;">🔥 Soul Imprints</h3> <<button "Refund All Essence">> <<if Meta.resetPerks()>> <<redo "AstralArchive">> <<else>> <<run Msg.flash("No Soul Essence to refund.")>> <</if>> <</button>> </div> <div class="perk-grid"> <<for _id, _perk range setup.MetaPerksDB>> <<set _currentLv to Meta.getPerkLv(_id)>> <div class="perk-card"> <h4 class="perk-title">_perk.name <span style="float:right; font-size: 0.8em; color: #888;">Lv. _currentLv / _perk.maxLv</span></h4> <div class="perk-desc">_perk.desc</div> <div style="height: 2rem;margin-top: auto; display: flex; justify-content: space-between; align-items: center;"> <<if _currentLv >= _perk.maxLv>> <span style="color: #8b0000; font-weight: bold;">[ Maxed Out ]</span> <<else>> <span style="color: #ccc; font-size: 0.9em;">Cost: <span class="soul-gem-text">💎 _perk.cost</span></span> <<if Meta.gems >= _perk.cost>> <<capture _id>> <<button "Imbue">> <<if setup.MetaManager.upgradePerk(_id)>> <<redo "AstralArchive">> <</if>> <</button>> <</capture>> <<else>> <span style="color: #555; font-size: 0.85em; text-transform: uppercase;">Not enough Essence</span> <</if>> <</if>> </div> </div> <</for>> </div> <</do>> <</widget>>
<<if visited() > 1 || Flag('skiptutor')>> <<set $chars.selected = null >> <div class="warren-panel"> <div class="content-panel"> <div class="flex-row-sb1" id="left-box" style="width: 100%;height: 100%;"> <<do tag "selected shoplist">> <<Infolist $chars.selected>> <div class="ui-row-center" style="width: 100%;margin-left: 8rem;"> <<ShowPortrait $chars.selected>> </div> <</do>> </div> <div class="leftbox-buttons"> <<do tag "selected shoplist">> <<if $chars.selected>> <<button "Genes">> <<replace "#left-box">> <<do tag "selected shoplist">> <div class="left-gene-box" style="display: flex;"> <div style="gap: 1rem;display: flex; flex-direction: column;"> <<TrainDataBox $chars.selected>> <div id="genemore"> <div style="text-align: center;font-size: 2rem;align-content: center; height: 100%;width: 100%;"><<GeneFullPanel $chars.selected "head">></div> </div> </div> <<GenesList $chars.selected>> </div> <</do>> <</replace>> <</button>> <<button "Stats">> <<replace "#left-box">> <<do tag "selected shoplist">> <<Infolist $chars.selected>> <<ShowPortrait $chars.selected>> <</do>> <</replace>> <</button>> <<button "Outfit">> <<if $chars.selected.type === "unique" || $chars.selected.type === "wrong">> <<run Msg.flash("Available for normal monsters only for now. Unique and Wrong types are not yet supported.")>> <<else>> <<replace "#warren-right">> <!-- 右侧放商店UI的容器 --> <div id="wardrobe-ui-panel" style="padding: 1rem;width: 100%;height: 100%;"></div> <!-- 调用 JS 初始化商店 --> <<run setTimeout(function() { window.WardrobeUI.init($chars.selected, 'wardrobe-ui-panel', 'portrait-container'); }, 200)>> <</replace>> <</if>> <</button>> <<button "Roles List">> <<replace "#warren-right">> <<MapList $chars.templeList>> <</replace>> <</button>> <</if>> <</do>> </div> </div> <div class="content-panel" id="warren-right"> <div class="monsterinfols"> <<MapList $chars.templeList>> </div> </div> </div> <</if>> <<set _FacHelp = "CodexEntry_Building_Chambers" >>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<includeplus "Monsters">> </div> <<include "UI_RightBar">> </div> </div>
<<widget NewMonster>> <<if _args[0]>> <<set _newmonster = createMonster(_args[0])>> <<else>> <<set _newmonster = createMonster()>> <</if>> <<set $addmonster = _newmonster>> <<unset _newmonster>> <</widget>> <<widget AddTrait>> <<set _tnum = _args[2] || 1 >> <<if def _args[1]>> <<set _tkey = _args[1]>> <<set _traitId = _args[1].split('-')[0] >> <<set _traitLv = _args[1].split('-')[1] || 1>> <<else>> <<set _tkey = setup.RandomTrait() >> <</if>> <<for _i=0; _i lt _tnum;_i++>> <<run _args[0].addTrait(_traitId, _traitLv)>> <</for>> <</widget>> <<widget NewSlave>> <<for _p to 0; _p lt $slave.capacity; _p++>> <<NewMonster>> <<run setup.initBodyTraining($addmonster)>> <<run $slave.slaveMap.set($addmonster.id, $addmonster)>> <<run $slave.slaveList.push($addmonster.id)>> <<unset $addmonster>> <</for>> <</widget>> <<widget AddMonster>> <<set _collnum = _args[0].race + _args[0].rank + "token" >> <<run setup.addSpeciesRank(_args[0].race, _args[0].rank)>> <<if !bestiaryManager.isLineageUnlocked(_args[0].race)>> <<run EventBus.emit("lineage:discover", { id: _args[0].race });>> <<run Msg.notify("Bestiary updated with new discovery.")>> <</if>> <<if !bestiaryManager.isSpeciesUnlocked(_args[0].rankId)>> <<run EventBus.emit("species:status_change", { id: _args[0].rankId, status: "contracted" });>> <<run Msg.notify("Bestiary updated with new discovery.")>> <</if>> <<if _args[0].type == "unique">> <<run Npc.changeList(_args[0].id,"npcList","templeList");>> <<run Npc.sortByIndex()>> <<else>> <<run $chars.dataMap.set(_args[0].id, _args[0]);>> <<run $chars.templeList.push(_args[0].id)>> <<set _text = _args[0].name + " moved into the Warren." >> <<run Msg.notify(_text);>> <</if>> <<run PopSystem.add(_args[0])>> <<set $tech.techPoint[_args[0].race] = ($tech.techPoint[_args[0].race] ?? 0) + _args[0].rank;>> <</widget>> <<widget DelMonster>> <<run setup.delMonster(_args[0])>> <</widget>> <<widget HeadImageByID>> <<set _imageSize = _args[0] || 128 >> <<set _imagechar = getMonsterByID(_args[0])>> <<if _imagechar?.type == "unique" && _imagechar.head>> <<set _headCimg = setup.ImagePath + _imagechar.head >> <<cimg _headCimg>> <<elseif _imagechar?.type == "unique" || _imagechar?.type == "wrong">> <<set _headCimg = setup.ImagePath + _imagechar.img >> <<cimg _headCimg>> <<else>> <div style="width: 100%;" @id="_imagechar.id"> <<portrait _imagechar _imageSize>> </div> <</if>> <</widget>> <<widget "PopTable">> <<set _loc to _args[0] || "temple">> <<set _stats to PopSystem.getLocationStats(_loc)>> <<set _list to PopSystem.getHousingTableData(_loc)>> <div class="pop-table-container"> <div style="display:flex; justify-content:space-between; align-items:center; padding:0.5rem; margin-bottom:0.5rem; background:rgba(0,0,0,0.2); border-radius:4px;"> <div> <span style="font-weight:bold; text-transform:uppercase; color:#cbd5e0; font-size:0.9rem;"> <i class="fas fa-map-marker-alt"></i> <<print _loc>> </span> </div> <div style="text-align:right; font-size:0.85rem;"> <span @style="_stats.isOver ? 'color:#fc8181; font-weight:bold' : 'color:#68d391'"> <i class="fas fa-users"></i> <<print PopSystem.formatNum(_stats.count)>> / <<print PopSystem.formatNum(_stats.cap)>> </span> <!-- Mood Effect --> <span style="margin-left:1rem; color:#a0aec0; border-left:1px solid #4a5568; padding-left:1rem;"> Mood Effect: <<if _stats.mood > 0>> <span style="color:#63b3ed; font-weight:bold;">+<<print _stats.mood>></span> <<elseif _stats.mood < 0>> <span style="color:#fc8181; font-weight:bold;"><<print _stats.mood>></span> <<else>> <span style="color:#718096;">0</span> <</if>> </span> </div> </div> <<if _list.length is 0>> <div style="padding:2rem; text-align:center; color:#718096; font-style:italic; background:rgba(0,0,0,0.1);"> No population assigned to this location. </div> <<else>> <div class="pt-row pt-head"> <div class="pt-col-name">Species / Share</div> <div class="pt-col-count">Total</div> <div class="pt-col-gender"> <div class="gender-cell"><i class="fas fa-venus" title="Female"></i></div> <div class="gender-cell"><i class="fas fa-mars" title="Male"></i></div> <div class="gender-cell"><i class="fas fa-transgender" title="Futa"></i></div> </div> </div> <<for _i, _item range _list>> <div class="pt-row" style="background:rgba(255,255,255,0.02);"> <div class="pt-col-name"> <div style="display:flex; justify-content:space-between; align-items:baseline;"> <span style="color:#e2e8f0; font-weight:bold;"><<print _item.name>></span> <span style="font-size:0.7rem; color:#718096;"><<print _item.shareStr>></span> </div> <div class="pt-progress-track" title="Population Share (Percentage of total location pop)"> <div class="pt-progress-bar" @style="'width:' + _item.share * 100 + '%'"></div> </div> </div> <div class="pt-col-count"> <span style="font-family:monospace; font-size:1rem; font-weight:bold; color:#fff;"> <<print PopSystem.formatNum(_item.count)>> </span> </div> <div class="pt-col-gender"> <div @class="'gender-cell g-girl' + (_item.genders.girl <= 0 ? ' is-empty' : '')"> <span><<print PopSystem.formatNum(_item.genders.girl)>></span> </div> <div @class="'gender-cell g-boy' + (_item.genders.boy <= 0 ? ' is-empty' : '')"> <span><<print PopSystem.formatNum(_item.genders.boy)>></span> </div> <div @class="'gender-cell g-futa' + (_item.genders.futa <= 0 ? ' is-empty' : '')"> <span><<print PopSystem.formatNum(_item.genders.futa)>></span> </div> </div> </div> <</for>> <</if>> </div> <</widget>>
<<widget "TraitsList">> <<for _trait range Object.entries(_args[0].traits)>> <<set [_id, _data] = _trait>> <<HoverTip '<<TraitCard _id>>'>> <span class="trait-tag"><<= setup.traitdata[_id].id.startsWith("talent")? setup.traitdata_racial[_id].icon : setup.traitdata[_id].icon>> Lv<<= _data.lv>></span> <</HoverTip>> <</for>> <</widget>> <<widget "EffectsList">> <<for _effect range Object.entries(_args[0].effects)>> <<set [_id, _data] = _effect>> <<HoverTip "<<= getItemDescByID(_id)>> <br> From: <<= getItemNameByID(_id)>> <br>Lv: _data.lv">> <div class="buff-icon">💡</div> <</HoverTip>> <</for>> <</widget>> <<widget "Infocard">> <<do tag "selected charStatus">> <<set _clid = _args[0] >> <<set _crid = _args[1] >> <<capture _clid _crid>> <<if _clid >> <div class="infocard"> <div class="infocard-traitslist"> <<run isVirgin(_clid)>> <<TraitsList _clid>> </div> <div class="infocardstats"> <div class="infocard-title"> <div id="name"> <span style="font-size: 1em;"> _clid.name</span> <<do tag "itemchecked">> <<EffectsList _clid>> <</do>> </div> <div id="staap"> <<HoverTip "<<= L.actionrecovery>>" >>❤️<<= _clid.currentAPFloor>><</HoverTip>> <<if _clid.harvest.id>> <<HoverTip "<<= L.harvest>>: <<= getiteminfo(_clid.harvest.id,'name')>><br> <<= getiteminfo(_clid.harvest.id,'desc')>><br><<= L.productionstock>><br><<= L.productalert>>" >>👨🌾<<= _clid.harvest.store>>/<<= _clid.getMaxStoreAp>><</HoverTip>><</if>> </div> </div> <div class="infocardicons"> <div id="race"> <span style="font-size: 1em;"><<= setup.bestiaryDB[_clid.race].icon>><<= setup.gender2pic[_clid.gender]>></span> </div> <div id="getxp"> <span style="font-size: 1em;">EXP </span> <<if _crid>><<set _laddxp = _crid.giveExp >><<else>><<set _laddxp = 0 >><</if>><meter @value="_clid.currentExp('level') + _laddxp " min="0" @max="_clid.upgradeXp('level')"></meter> <span style="font-size: 1em;">+<<= _laddxp>></span> </div> <<HoverTip "Level: <<= _clid.level.lv>> Max: <<= _clid.level.max>>">><span style="font-size: 1em;"><<= `Lv _clid.level.lv / _clid.level.max`>></span><</HoverTip>> <<HoverTip "<<= setup.renderRarity(_clid.rank)>> Potential Tier / Bloodline">><<= setup.renderRarity(_clid.rank, true)>><</HoverTip>> <<HoverTip "Growth Potential: sums a character’s attribute talents, driving faster core attribute growth. Higher values suggest worth for nurturing, not current attribute values.">>Growth <<= _clid.getGrowth>><</HoverTip>> </div> <div id="coreAttr"> <<HoverTip '<<StatCard _clid "sta">>'>><div id="infos" @data-rank="_clid.getRank('sta')"><span id="alv">STA <<= _clid.sta.lv>></span><span id="aop"><<= _clid.getRank("sta")>></span></div><</HoverTip>> <<HoverTip '<<StatCard _clid "str">>'>><div id="infos" @data-rank="_clid.getRank('str')"><span id="alv">STR <<= _clid.str.lv>></span><span id="aop"><<= _clid.getRank("str")>></span></div><</HoverTip>> <<HoverTip '<<StatCard _clid "dex">>'>><div id="infos" @data-rank="_clid.getRank('dex')"><span id="alv">DEX <<= _clid.dex.lv>></span><span id="aop"><<= _clid.getRank("dex")>></span></div><</HoverTip>> <<HoverTip '<<StatCard _clid "wil">>'>><div id="infos" @data-rank="_clid.getRank('wil')"><span id="alv">WIL <<= _clid.wil.lv>></span><span id="aop"><<= _clid.getRank("wil")>></span></div><</HoverTip>> <<HoverTip '<<StatCard _clid "cha">>'>><div id="infos" @data-rank="_clid.getRank('cha')"><span id="alv">CHA <<= _clid.cha.lv>></span><span id="aop"><<= _clid.getRank("cha")>></span></div><</HoverTip>> <<HoverTip '<<StatCard _clid "fer">>'>><div id="infos" @data-rank="_clid.getRank('fer')"><span id="alv">FER <<= _clid.fer.lv>></span><span id="aop"><<= _clid.getRank("fer")>></span></div><</HoverTip>> <div id="energy"> <<HoverTip `L.energycost`>><span style="font-size: 1em;"><<= L.cost>><<= setup.harvestCost(_clid)>>⚡</span><</HoverTip>> <<HoverTip `L.luckboost`>><span style="font-size: 1em;"><<= L.fer>><<= `${_clid.giveFer}%`;>></span><</HoverTip>> </div> </div> </div> </div> <</if>> <</capture>><</do>> <</widget>>
<<widget "Infolist">> <<script>> window.toggleModule = function(moduleId) { const module = document.getElementById(moduleId); if (module) { module.classList.toggle('plexpanded'); module.classList.toggle('plcollapsed'); } } <</script>> <<if _args[0]>> <<set _infolistRole = _args[0] >> <div class="profile-plane"> <div class="module profile-header"> <div class="module-title" id="profile" onclick="toggleModule(this.id)"> <<do tag "rename">><<= _infolistRole.name>><</do>> <span class="toggle-indicator"></span></div> <div class="module-content"> <div class="stat-item"> <span> <<= _infolistRole.race.toUpperFirst()>> <span style="font-size: 1em;"><<= setup.bestiaryDB[_infolistRole.race].icon>></span> <span><<if _infolistRole.type === "hybrid">><<= "Hybrid">><</if>></span> </span> <span><<HoverTip "Potential Tier / Bloodline">><<= setup.renderRarity(_infolistRole.rank)>><</HoverTip>></span> </div> <div class="stat-item"> <span><<HoverTip setup.speciesDB[_infolistRole.rankId].desc>>Rank: <<= setup.speciesDB[_infolistRole.rankId].baseInfo.name>><</HoverTip>></span> <span><<HoverTip "Growth Potential: sums a character’s attribute talents, driving faster core attribute growth. Higher values suggest worth for nurturing, not current attribute values.">>GP: <<= _infolistRole.getGrowth>><</HoverTip>></span> </div> <div class="stat-item"> <span><<= L.gender>>: <span style="font-size: 1em;"><<= setup.gender2pic[_infolistRole.gender]>></span></span> <span><<HoverTip "Appearance: _infolistRole.getLook <br>Representing a character's physical attractiveness,this attribute is heritable.">>Look: <<= setup.lookTier.tag(_infolistRole.getLook)>><</HoverTip>></span> </div> <div class="stat-item"> <span><<= L.mood>>: <<= _infolistRole.mood.vl>></span> <span>AP: <<HoverTip '<<= L.actionrecovery>>'>>❤️<<= _infolistRole.currentAPFloor>><</HoverTip>></span> </div> <div class="stat-full"> <meter @value="_infolistRole.currentExp('level')" min="0" @max="_infolistRole.upgradeXp('level')"></meter> <div class="stat-item"><span><<HoverTip "(Max: <<= _infolistRole.level.max>>)">><<= L.lv>>: <<=_infolistRole.level.lv>> / Max: <<= _infolistRole.level.max>><</HoverTip>></span> <span><<if _infolistRole.level.lv < _infolistRole.level.max>>XP: <<= _infolistRole.progressText('level')>><<else>>LVMAX<</if>></span></div> </div> </div> </div> <div class="module"> <div class="module-title" id="coreattributes" onclick="toggleModule(this.id)">core attributes <span class="toggle-indicator"></span></div> <div class="module-content attribute-grid"> <<RadarImage _args[0]>> </div> </div> <div class="module"> <div class="module-title" id="subattributes" onclick="toggleModule(this.id)">Sub Attributes <span class="toggle-indicator"></span></div> <div class="module-content"> <div class="stat-item" style="flex-wrap: wrap;"> </div> <div class="stat-item"> <span>Parental: <<HoverTip _infolistRole.parental[0].name>><<if _infolistRole.parental[0].id === null>>📜<<elseif _infolistRole.parental[0].id === "you">>👑<<else>><<= setup.bestiaryDB[_infolistRole.parental[0].race].icon>><</if>><</HoverTip>> <<HoverTip _infolistRole.parental[1].name>><<if _infolistRole.parental[1].id === null>>📜<<elseif _infolistRole.parental[1].id === "you">>👑<<else>><<= setup.bestiaryDB[_infolistRole.parental[1].race].icon>><</if>><</HoverTip>></span> <<HoverTip "<<if _infolistRole.offspring.length==0>>None<<else>><<= _infolistRole.offspring.map(o => o.name).join(', ')>><</if>>">><span>Offspring: <<= _infolistRole.offspring.length>></span><</HoverTip>> </div> <div class="stat-item"> <<HoverTip "Ritual Experience Gift sets the experience points granted to others in summoning rituals, linked to STR.">><span>GiveExp: <<= _infolistRole.giveExp>></span><</HoverTip>> <<HoverTip "Experience Amplification multiplies experience points received from summoning rituals, tied to the character’s CHA.">><span>GetExp: <<= `${(_infolistRole.getExpRate * 100).toFixed(0)}%`>></span><</HoverTip>> </div> <div class="stat-item"> <<HoverTip "Ritual Luck Blessing provides luck in summoning rituals, also indicating pregnancy chance if conditions are met (see probability table).">><span>Give🌱: <<= `${_infolistRole.giveFer}%`>></span><</HoverTip>> <<HoverTip "Ritual Energy Cost determines the energy consumed by the character during summoning rituals, linked to DEX.">><span>Cost⚡: <<= setup.harvestCost(_infolistRole)>></span><</HoverTip>> </div> <div class="stat-item"> <<HoverTip "the action points gained each day, tied to the character’s STA value.">><span>Daily❤️: <<= _infolistRole.dailyAP.toFixed(1)>></span><</HoverTip>> <<HoverTip "Quest Reward Amplification indicates the percentage increase in rewards gained upon completing tasks. Higher values yield greater rewards from quests.">><span>Value: <<= `${(_infolistRole.value.pricemul * 100).toFixed(0)}%`>></span><</HoverTip>> </div> <<if _infolistRole.harvest.id>> <div class="stat-item"> <<HoverTip "<<= getiteminfo(_infolistRole.harvest.id,'name')>> <br> <<= getiteminfo(_infolistRole.harvest.id,'desc')>><br>">><span>Harvest: 👨🌾</span><</HoverTip>> <<HoverTip "Harvest Yield tracks the current and maximum capacity of a Monster’s unique produce, tied to WIL.">><span><<= L.productionstock>>: <<= (_infolistRole.harvest?.store ?? 0)>>/<<= _infolistRole.getMaxStoreAp>></span><</HoverTip>> </div> <</if>> <div class="stat-item"> <span>Stamina: <<= _infolistRole.getStaminaCurrent>> / <<= _infolistRole.getStaminaMax>></span> <span>Mental: <<= _infolistRole.getMentalCurrent>> / <<= _infolistRole.getMentalMax>></span> </div> </div> </div> <div class="module"> <div class="module-title" id="traits" onclick="toggleModule(this.id)"><<= setup.langbank.traits[$lang]>> <span class="toggle-indicator"></span></div> <div class="module-content trait-grid"> <<TraitsList _infolistRole>> </div> </div> <div class="module"> <div class="module-title" id="effects" onclick="toggleModule(this.id)"><<= setup.langbank.effects[$lang]>> <span class="toggle-indicator"></span></div> <div class="module-content effect-grid"> <<EffectsList _infolistRole>> </div> </div> <div class="module"> <div class="module-title" id="states" onclick="toggleModule(this.id)"><<= "States">> <span class="toggle-indicator"></span></div> <div class="module-content states-grid"> <<TrainStatesBar _infolistRole>> </div> </div> </div> <</if>> <</widget>>
<<widget "MapList">> <<run setup.refreshDirtyMonsters()>> <div class="maplist-container"> <div class="filter-panel"> <div class="list-info"> Total: <strong id="display-count">0</strong> / <strong id="total-count">0</strong> </div> <div class="filter-section"> <div class="search-box"> <input type="text" class="search-input" id="search-input" placeholder="Name /Tag ..." style="min-width: 8rem;"> <div class="search-icon" id="search-btn"> <i class="fas fa-search"></i> </div> </div> </div> <div class="filter-section"> <div class="section-title"> <span>Species Filter</span> <i class="fas fa-sliders-h"></i> </div> <div class="grid-cols-2 gap-4" id="race-icons"> </div> </div> <!-- <i class="fas fa-sliders-h"></i> --> <div class="filter-section"> <div class="section-title"> <span>Attribute Filter</span> <i class="fas fa-sliders-h"></i> </div> <div class="filter-options"> <div><i class="fa-regular fa-gem"></i></div> <select class="filter-select" id="rarity-filter"> <option value=7 disabled>Mythic</option> <option value=6 disabled>Legendary</option> <option value=5 disabled>Epic</option> <option value=4 disabled>Rare</option> <option value=3>Uncommon</option> <option value=2>Common</option> <option value=1>Lesser</option> <option value=0>Cursed</option> <option value="all" selected>All</option> </select> </div> <div class="filter-options"> <div><i class="fa-solid fa-venus-mars"></i></div> <select class="filter-select" id="gender-filter"> <option value="other" disabled>Other</option> <option value="futa">Futa</option> <option value="boy">Boy</option> <option value="girl">Girl</option> <option value="all" selected>All</option> </select> </div> </div> <div class="filter-section tag-cloud"> <div class="section-title"> <span>Tags Filter</span> <i class="fa-solid fa-tags"></i> </div> <div class="flex-row-wrap mt-8 gap-3" id="tags-container"> </div> </div> <div class="filter-section" style="margin-top: auto;"> <div class="filter-actions"> <button class="filter-btn" id="reset-filters">Reset</button> <button class="filter-btn primary" id="apply-filters">Apply</button> </div> </div> </div> <div class="content-panel"> <div class="sort-bar"> <div class="sort-list"> <div class="sort-btn" data-sort="order" title="Sort by Original Order"> <i class="fas fa-history"></i> </div> <div class="sort-btn" data-sort="name" title="Sort by Name"> <i class="fas fa-font"></i> </div> <div class="sort-btn" data-sort="level" title="Sort by Level"> <i class="fas fa-level-up-alt"></i> </div> <div class="sort-btn" data-sort="hp" title="Sort by AP"> <i class="fas fa-heart"></i> </div> <div class="sort-btn" data-sort="harvest" title="Sort by Harvest"> <i class="fa-solid fa-bottle-droplet"></i> </div> <div class="sort-btn" data-sort="look" title="Sort by Look"> <i class="fa-solid fa-face-kiss-beam"></i> </div> <div class="sort-btn" data-sort="sta" title="Sort by Stamina"> <i class="fa-solid fa-hourglass-half"></i> </div> <div class="sort-btn" data-sort="str" title="Sort by Strength"> <i class="fas fa-fist-raised"></i> </div> <div class="sort-btn" data-sort="cha" title="Sort by Charisma"> <i class="fas fa-comments"></i> </div> <div class="sort-btn" data-sort="dex" title="Sort by Dexterity"> <i class="fas fa-wind"></i> </div> <div class="sort-btn" data-sort="wil" title="Sort by Willpower"> <i class="fas fa-eye"></i> </div> <div class="sort-btn" data-sort="fer" title="Sort by Fertility"> <i class="fa-solid fa-seedling"></i> </div> </div> <div class="sort-list"> <div class="view-btn" data-view="card" title="Card View"> <i class="fas fa-th-large"></i> </div> <div class="view-btn" data-view="list" title="List View"> <i class="fas fa-list"></i> </div> <div class="view-btn" data-view="compact" title="Compact View"> <i class="fas fa-grip-horizontal"></i> </div> </div> </div> <div class="character-list card-view" id="character-list"> </div> <div class="action-bar"> <div class="summon-management" id="summon-management" style="display: none;"> <div class="summon-list" id="summon-list"> <<do tag "jionslot">> <<button "<div class='filter-btn summon-btn'>Jion Left Solt<br>🌌<<= $summon.slots[0]?.name || 'Nobody'>></div>">> <<set $summon.slots[0] = $chars.selected >> <<set $summon.slotsId[0] = $chars.selected.id >> <<if $summon.slots[0]?.id == $summon.slots[1]?.id>> <<set $summon.slots[1] = null >> <<set $summon.slotsId[1] = null >> <</if>> <<redo "jionslot">> <</button>> <<button "<div class='filter-btn summon-btn'>Jion Right Solt<br>🌌<<= $summon.slots[1]?.name || 'Nobody'>></div>">> <<set $summon.slots[1] = $chars.selected>> <<set $summon.slotsId[1] = $chars.selected.id >> <<if $summon.slots[1]?.id == $summon.slots[0]?.id>> <<set $summon.slots[0] = null >> <<set $summon.slotsId[0] = null >> <</if>> <<redo "jionslot">> <</button>> <<button "<div class='filter-btn summon-btn'>Goto Summon</div>">> <<goto "Summon_UI">> <</button>><</do>> </div> </div> <div class="tag-management" id="tag-management" style="display: none;"> <div class="tag-cloud-list" id="tag-cloud-list"> </div> <div class="tag-input-row"> <input type="text" class="tag-input" id="tag-input" placeholder="Enter new tag. Supports search and multi-select filtering"> <button class="tag-add-btn" id="tag-add-btn">Add</button> </div> </div> <div class="action-title"> <span style="width: 15rem;">Character Actions</span> <div class="batch-controls" style="display:flex; gap:0.5rem; align-items:center;"> <button class="filter-btn" id="batch-toggle" style="padding: 0.25rem 0.5rem; font-size: 0.8rem;">Batch: OFF</button> <button class="filter-btn" id="batch-select-all" style="flex: none;padding: 0.25rem 0.5rem; font-size: 0.8rem; display:none;">Select All Filtered</button> </div> <span id="action-target" style="margin-left: auto;">No Selected</span> </div> <div class="grid-cols-auto-fill-4 gap-8"> <button class="action-btn" data-action="tag" id="tag-action-btn"> <i class="fa-solid fa-tags"></i> <span>Tags</span> </button> <<if setup.isCurrentView("Monsters")>><button class="action-btn" data-action="summon"> <i class="fa-solid fa-user-plus"></i> <span>Summon</span> </button><</if>> <<if setup.isCurrentView("Monsters")>><button class="action-btn" data-action="harvest"> <i class="fa-solid fa-bucket"></i> <span>Harvest</span> </button><</if>> <<if setup.isCurrentView("Monsters")>><button class="action-btn" data-action="moveout"> <i class="fa-solid fa-person-walking-dashed-line-arrow-right"></i> <span>Relocate</span> </button><</if>> <<if Flag('entrain') == 2 && setup.isCurrentView("Monsters")>><button class="action-btn" data-action="train"> <i class="fa-solid fa-person-praying"></i> <span>Training</span> </button><</if>> <<if Flag('enmeeting') == 2>><button class="action-btn" data-action="stopjob"> <i class="fas fa-mug-hot"></i> <span>Unassign</span> </button><</if>> <button class="action-btn" data-action="rename"> <i class="fa-solid fa-pen"></i> <span>Rename</span> </button> </div> <div class="team-selection" id="team-selection" style="display: none;"> <div class="section-title" style="font-size: 0.95rem; margin-bottom: 0.5rem;"> <span>Select Team</span> </div> <div class="team-options" id="team-options"> </div> </div> </div> </div> </div> <<done>> <<script>> let charMap = State.temporary.args[1] || State.variables.chars.dataMap; let charList = State.temporary.args[0]; let availableTags = State.variables.chars.availableTags; const teamOptions = ["team1", "team2", "team3", "team4"]; let isBatchMode = false; let batchSelectedIds = new Set(); initDataStructures(); initClassIcons(); initFilterOptions(); initTagCloud(); initTeamOptions(); initViewState(); renderCharacterList(); initEventListeners(); updateCounts(); function initDataStructures() { } function initViewState() { const savedViewMode = State.variables.chars.viewMode || 'card'; const savedSortMode = State.variables.chars.sortBy || 'ap'; document.querySelector(`.view-btn[data-view="${savedViewMode}"]`)?.classList.add('active'); document.querySelector(`.sort-btn[data-sort="${savedSortMode}"]`)?.classList.add('active'); } function initClassIcons() { const classIconsContainer = document.getElementById('race-icons'); classIconsContainer.innerHTML = ''; const racekeys = Object.keys(setup.iconSpriteData.species64.frames); racekeys.forEach(key => { const classIcon = document.createElement('div'); classIcon.className = 'race-icon'; classIcon.dataset.classId = key; if (State.variables.chars.filters.races.includes(key)) { classIcon.classList.add('active'); }; const classIconHTML = ` <<showframe 'species64' "${key}" 3.5>> `; $(classIcon).wiki(classIconHTML); classIconsContainer.appendChild(classIcon); }); } function initFilterOptions() { const selectRarityElement = document.getElementById('rarity-filter'); const selectGenderElement = document.getElementById('gender-filter'); if (selectRarityElement) { selectRarityElement.value = State.variables.chars.filters.rarity; } if (selectGenderElement) { selectGenderElement.value = State.variables.chars.filters.gender; } } function initTagCloud() { const tagsContainer = document.getElementById('tags-container'); const tagCloudList = document.getElementById('tag-cloud-list'); tagsContainer.innerHTML = ''; tagCloudList.innerHTML = ''; availableTags.forEach(tag => { const tagElement = document.createElement('div'); tagElement.className = 'tag-item'; tagElement.textContent = tag; tagElement.dataset.tag = tag; if (State.variables.chars.filters.tags.includes(tag)) { tagElement.classList.add('active'); }; tagsContainer.appendChild(tagElement); }); availableTags.forEach(tag => { const tagElement = document.createElement('div'); tagElement.className = 'tag-cloud-item'; tagElement.textContent = tag; tagElement.dataset.tag = tag; tagCloudList.appendChild(tagElement); }); } function initTeamOptions() { const teamOptionsContainer = document.getElementById('team-options'); teamOptionsContainer.innerHTML = ''; teamOptions.forEach(team => { const teamOption = document.createElement('label'); teamOption.className = 'team-option'; teamOption.innerHTML = ` <input type="radio" name="team" value="${team}"> <span>${team}</span> `; teamOptionsContainer.appendChild(teamOption); }); } function initEventListeners() { document.getElementById('search-input').addEventListener('input', function() { State.variables.chars.filters.search = this.value; applyFilters(); }); document.getElementById('search-btn').addEventListener('click', function() { applyFilters(); }); document.querySelectorAll('.race-icon').forEach(icon => { icon.addEventListener('click', function() { const classId = this.dataset.classId; this.classList.toggle('active'); const activeraces = Array.from(document.querySelectorAll('.race-icon.active')) .map(icon => icon.dataset.classId); State.variables.chars.filters.races = activeraces; applyFilters(); }); }); document.getElementById('rarity-filter').addEventListener('change', function() { State.variables.chars.filters.rarity = this.value; applyFilters(); }); document.getElementById('gender-filter').addEventListener('change', function() { State.variables.chars.filters.gender = this.value; applyFilters(); }); document.querySelectorAll('.tag-item').forEach(tag => { tag.addEventListener('click', function() { const tagName = this.dataset.tag; this.classList.toggle('active'); const activeTags = Array.from(document.querySelectorAll('.tag-item.active')) .map(tag => tag.dataset.tag); State.variables.chars.filters.tags = activeTags; applyFilters(); }); }); document.querySelectorAll('.sort-btn').forEach(btn => { btn.addEventListener('click', function() { const sortId = this.dataset.sort; if (sortId === State.variables.chars.sortBy) { State.variables.chars.sortDirection = State.variables.chars.sortDirection === 'asc' ? 'desc' : 'asc'; } else { State.variables.chars.sortBy = sortId; State.variables.chars.sortDirection = 'desc'; } document.querySelectorAll('.sort-btn').forEach(b => { b.classList.remove('active'); }); this.classList.add('active'); applyFilters(); }); }); document.querySelectorAll('.view-btn').forEach(btn => { btn.addEventListener('click', function() { const viewMode = this.dataset.view; document.querySelectorAll('.view-btn').forEach(b => { b.classList.remove('active'); }); this.classList.add('active'); State.variables.chars.viewMode = viewMode; renderCharacterList(); }); }); document.getElementById('reset-filters').addEventListener('click', function() { resetFilters(); }); document.getElementById('apply-filters').addEventListener('click', function() { applyFilters(); }); document.querySelectorAll('.action-btn').forEach(btn => { btn.addEventListener('click', function() { const action = this.dataset.action; handleAction(action); }); }); document.getElementById('tag-add-btn').addEventListener('click', function() { addNewTag(); }); document.getElementById('tag-input').addEventListener('keypress', function(e) { if (e.key === 'Enter') { addNewTag(); } }); document.getElementById('tag-cloud-list').addEventListener('click', function(e) { if (e.target.classList.contains('tag-cloud-item')) { toggleCharacterTag(e.target.dataset.tag); } }); document.getElementById('team-options').addEventListener('change', function(e) { if (e.target.type === 'radio') { assignCharacterToTeam(e.target.value); } }); document.getElementById('character-list').addEventListener('click', function(e) { const card = e.target.closest('.character-card'); if (!card) return; const charId = card.dataset.characterId; const actionIcon = e.target.closest('.action-icon'); if (actionIcon) { const action = actionIcon.dataset.action; if (action === 'favorite') { toggleFavorite(charId); } e.stopPropagation(); } else { if (isBatchMode) { toggleBatchSelection(charId, card); } else { selectCharacter(charId); } } }); document.getElementById('batch-toggle').addEventListener('click', function() { isBatchMode = !isBatchMode; this.textContent = isBatchMode ? 'Batch: ON' : 'Batch: OFF'; this.style.backgroundColor = isBatchMode ? 'var(--accent-red)' : ''; this.style.color = isBatchMode ? 'var(--text-white)' : ''; const selectAllBtn = document.getElementById('batch-select-all'); selectAllBtn.style.display = isBatchMode ? 'block' : 'none'; if (!isBatchMode) { batchSelectedIds.clear(); renderCharacterList(); document.getElementById('action-target').textContent = State.variables.chars.selected ? State.variables.chars.selected.name : 'No Selected'; updateTagCloud(State.variables.chars.selected?.id); } else { document.getElementById('action-target').textContent = `Selected: ${batchSelectedIds.size}`; updateTagCloud(); } }); document.getElementById('batch-select-all').addEventListener('click', function() { const filteredCharacters = filterCharacters(); filteredCharacters.forEach(id => batchSelectedIds.add(id)); renderCharacterList(); document.getElementById('action-target').textContent = `Selected: ${batchSelectedIds.size}`; updateTagCloud(); }); } function resetFilters() { State.variables.chars.filters = { search: "", races: [], rarity: "all", gender: "all", tags: [] }; document.getElementById('search-input').value = ''; document.querySelectorAll('.race-icon').forEach(icon => icon.classList.remove('active')); document.getElementById('rarity-filter').value = 'all'; document.getElementById('gender-filter').value = 'all'; document.querySelectorAll('.tag-item').forEach(tag => tag.classList.remove('active')); applyFilters(); } function applyFilters() { renderCharacterList(); updateCounts(); } function renderCharacterList() { const characterListContainer = document.getElementById('character-list'); let filteredCharacters = filterCharacters(); filteredCharacters = sortCharacters(filteredCharacters); document.getElementById('display-count').textContent = filteredCharacters.length; characterListContainer.className = `character-list ${State.variables.chars.viewMode}-view`; characterListContainer.innerHTML = ''; if (filteredCharacters.length === 0) { let emptyMessage = ` <div class="empty-state"> <i class="fas fa-search"></i> <h3>No Matching Characters Found</h3> <p>Try adjusting filter conditions or search keywords</p> `; const activeTags = State.variables.chars.filters.tags; if (activeTags.length === 1) { const activeTag = activeTags[0]; emptyMessage += ` <div class="empty-state-actions" style="margin-top: 20px;"> <button class="delete-tag-btn" data-tag="${activeTag}" style="padding: 8px 16px; background: var(--accent-red); color: white; border: none; border-radius: 4px; cursor: pointer;"> <i class="fas fa-trash"></i> Delete Tag "${activeTag}" </button> </div> `; } emptyMessage += `</div>`; characterListContainer.innerHTML = emptyMessage; const deleteBtn = characterListContainer.querySelector('.delete-tag-btn'); if (deleteBtn) { deleteBtn.addEventListener('click', function() { const tagToDelete = this.dataset.tag; deleteUnusedTag(tagToDelete); }); } return; } const fragment = document.createDocumentFragment(); filteredCharacters.forEach(characterId => { const characterCard = createCharacterCard(characterId); fragment.appendChild(characterCard); }); characterListContainer.appendChild(fragment); } function deleteUnusedTag(tag) { setup.Popup(`Delete tag "${tag}"? This will remove it from all characters.`, { buttons: [ "cancel", { confirm: () => confirmdeleteUnusedTag() }] }); const confirmdeleteUnusedTag = function() { let affectedCount = 0; charList.forEach(characterid => { const character = charMap.get(characterid); const index = character.tags.indexOf(tag); if (index !== -1) { character.tags.splice(index, 1); charMap.set(characterid, character); affectedCount++; } }); const tagIndex = availableTags.indexOf(tag); if (tagIndex !== -1) { availableTags.splice(tagIndex, 1); } State.variables.chars.filters.tags = State.variables.chars.filters.tags.filter(t => t !== tag); updateTagCloudUI(); renderCharacterList(); if (affectedCount > 0) { Msg.flash(`Tag "${tag}" removed from ${affectedCount} characters`); } else { Msg.flash(`Tag "${tag}" deleted`); } } } function filterCharacters() { const allCharacters = charList; return allCharacters.filter(characterId => { const character = charMap.get(characterId); if (State.variables.chars.filters.search) { const searchLower = State.variables.chars.filters.search.toLowerCase(); const nameMatch = character.name.toLowerCase().includes(searchLower); const classMatch = character.race.toLowerCase().includes(searchLower); const tagMatch = character.tags.some(tag => tag.toLowerCase().includes(searchLower)); if (!nameMatch && !classMatch && !tagMatch) { return false; } } if (State.variables.chars.filters.races.length > 0) { let characterClassId = ''; switch(character.race) { case 'xxx': characterClassId = 'xxx'; break; default: characterClassId = character.race.toLowerCase().replace(/\s+/g, ''); } if (!State.variables.chars.filters.races.includes(characterClassId)) { return false; } } if (State.variables.chars.filters.rarity !== 'all' && character.rank !== parseInt(State.variables.chars.filters.rarity)) { return false; } if (State.variables.chars.filters.gender !== 'all' && character.gender !== State.variables.chars.filters.gender) { return false; } if (State.variables.chars.filters.tags.length > 0) { const hasMatchingTag = State.variables.chars.filters.tags.some(tag => character.tags.includes(tag) ); if (!hasMatchingTag) { return false; } } return true; }); } function sortCharacters(characterIds) { return characterIds.sort((a, b) => { let aValue, bValue; switch(State.variables.chars.sortBy) { case 'order': aValue = charList.indexOf(a); bValue = charList.indexOf(b); break; case 'name': aValue = charMap.get(a).name; bValue = charMap.get(b).name; break; case 'level': aValue = charMap.get(a).level.lv; bValue = charMap.get(b).level.lv; break; case 'hp': aValue = charMap.get(a).stamina.currentAP; bValue = charMap.get(b).stamina.currentAP; break; case 'harvest': aValue = charMap.get(a).stamina.currentAP + charMap.get(a).harvest.store; bValue = charMap.get(b).stamina.currentAP + charMap.get(a).harvest.store; break; case 'look': aValue = charMap.get(a).getLook; bValue = charMap.get(b).getLook; break; case 'sta': aValue = charMap.get(a).sta.lv; bValue = charMap.get(b).sta.lv; break; case 'str': aValue = charMap.get(a).str.lv; bValue = charMap.get(b).str.lv; break; case 'cha': aValue = charMap.get(a).cha.lv; bValue = charMap.get(b).cha.lv; break; case 'dex': aValue = charMap.get(a).dex.lv; bValue = charMap.get(b).dex.lv; break; case 'wil': aValue = charMap.get(a).wil.lv; bValue = charMap.get(b).wil.lv; break; case 'fer': aValue = charMap.get(a).fer.lv; bValue = charMap.get(b).fer.lv; break; default: aValue = charMap.get(a).index; bValue = charMap.get(b).index; }; if (State.variables.chars.sortDirection === 'asc') { return aValue > bValue ? 1 : -1; } else { return aValue < bValue ? 1 : -1; } }); } function createCharacterCard(characterId) { const character = charMap.get(characterId); const card = document.createElement('div'); const isSelected = isBatchMode ? batchSelectedIds.has(character.id) : (State.variables.chars.selected?.id === character.id); card.className = `character-card ${isSelected ? 'selected' : ''} ${character.alivedays ===1 ? 'avatar-new' : ''} ${State.variables.summon.slotsId.includes(character.id) ? 'avatar-rift' : ''}`; card.dataset.characterId = character.id; const rarityColor = setup.rarityData[character.rank].UIColor; const traitsAll = Object.entries(character.traits || {}).map(([id, data]) => { const icon = setup.getTraitIconByID(id) || '✨'; const name = setup.getTraitNameByID(id) || id; const tier = setup.getTraitTierByID(id) || "Base"; const lv = data.lv || 1; return `<div class="character-tag" title="${name} Lv.${lv} Tier.${tier}">${icon}<sub class="trait-sub-lv">${lv}</sub><sup class="trait-sup-tier">${tier}</sup></div>`; }).join(''); const tagsAll = character.tags.map(tag => `<span class="character-tag" title="Custom Tag">${tag}</span>`).join(''); const raceIcon = setup.bestiaryDB[character.race]?.icon || ""; const genderPic = setup.gender2pic[character.gender] || ""; const uniqueIcon = character.type == "unique" ? "🔒" : ""; const workTags = character.worktags || ""; const stunIcon = character.traits["PlaneStun"] ? "💫" : ""; let uniqueTags = [raceIcon, genderPic, uniqueIcon, workTags, stunIcon ]; let uniqueTags2nd = [workTags, stunIcon ]; const hybridIcon = character.type == "hybrid" ? "🧬" : ""; const wrongIcon = character.type == "wrong" ? "🖤" : ""; const pregIcon = character.preg > 0 ? "🤰" : ""; const virginIcon = isVirgin(character) ? "🌸" : ""; uniqueTags = [raceIcon, genderPic, uniqueIcon, hybridIcon, wrongIcon, pregIcon, workTags, virginIcon, stunIcon ]; uniqueTags2nd = [hybridIcon, wrongIcon, pregIcon, workTags, stunIcon ]; const uniqueTagsHtml = uniqueTags .filter(tag => tag && tag.toString().trim() !== "") .map(tag => `<span>${tag}</span>`) .join(""); const uniqueTagsHtml2nd = uniqueTags2nd .filter(tag => tag && tag.toString().trim() !== "") .map(tag => `<span>${tag}</span>`) .join(""); const noUniqueTagsHtml = Object.entries(character.traits || {}).map(([id, data]) => { if (setup.traitdata[id].unheredity === true) return; const icon = setup.getTraitIconByID(id) || '✨'; const name = setup.getTraitNameByID(id) || id; const tier = setup.getTraitTierByID(id) || "Base"; const lv = data.lv || 1; return `<span class="character-tag" title="${name} Lv.${lv} Tier.${tier}">${icon}<sub class="trait-sub-lv">${lv}</sub><sup class="trait-sup-tier">${tier}</sup></span>`; }).join(''); if (State.variables.chars.viewMode === 'card') { const htmlContent = ` <div class="card-header"> <div class="character-avatar" style="background-color: ${character.color};"> <<HeadImageByID ${character.id}>> </div> <div class="character-name" style="color:${rarityColor}">${character.name}</div> <div class="action-icon" data-action="favorite" title="${character.favorited ? 'Remove Favorite' : 'Add Favorite'}"> <i class="${character.favorited ? 'fas' : 'far'} fa-star"></i> </div> </div> <div class="card-footer" style="padding: 0 0.5rem 0.25rem;"> <div class="character-tags">${uniqueTagsHtml}</div> </div> <div class="card-body"> <div class="character-stats"> <div class="character-race"> <span class="race-badge">Lv.${character.level.lv}</span> </div> <div class="stat-solt"> <div class="stat-label">💖</div> <div class="stat-value">${character.currentAPFloor}</div> </div> <<if ${character.race != "human"}>><div class="stat-solt"> <div class="stat-label">👨🌾</div> <div class="stat-value">${character.harvest.store}</div> </div><</if>> </div> </div> <div class="card-footer"> <div class="character-tags"> ${noUniqueTagsHtml} ${character.tags.slice(0, 3).map(tag => `<span class="character-tag">${tag}</span>`).join('')} ${character.tags.length > 3 ? `<span class="character-tag">+${character.tags.length - 3}</span>` : ''} </div> </div> `; card.innerHTML = ""; $(card).wiki(htmlContent); } else if (State.variables.chars.viewMode === 'list') { const htmlContent = ` <div> <div class="character-info"> <div class="character-name" style="color:${rarityColor}">${character.name}</div> <div class="character-race"><<= setup.bestiaryDB["${character.race}"].icon>><<= setup.gender2pic["${character.gender}"]>></div> <div class="character-stats"> <div class="stat-solt"> <div class="stat-label">Level</div> <div class="stat-value">${character.level.lv}</div> </div> <div class="stat-solt"> <div class="stat-label">AP</div> <div class="stat-value">💖${character.currentAPFloor}</div> </div> <div class="stat-solt"> <div class="stat-label">Look</div> <div class="stat-value">${character.getLook}</div> </div> <div class="stat-solt"> <div class="stat-label">STA</div> <div class="stat-value">${character.sta.lv}</div> </div> <div class="stat-solt"> <div class="stat-label">STR</div> <div class="stat-value">${character.str.lv}</div> </div> <div class="stat-solt"> <div class="stat-label">CHA</div> <div class="stat-value">${character.cha.lv}</div> </div> <div class="stat-solt"> <div class="stat-label">DEX</div> <div class="stat-value">${character.dex.lv}</div> </div> <div class="stat-solt"> <div class="stat-label">WIL</div> <div class="stat-value">${character.wil.lv}</div> </div> <div class="stat-solt"> <div class="stat-label">FER</div> <div class="stat-value">${character.fer.lv}</div> </div> </div> </div> <div style="line-height: 1.4;gap: 4px;display: flex;flex-wrap: wrap;">${uniqueTagsHtml2nd} ${traitsAll} ${tagsAll}</div> </div> <div class="character-actions"> <div class="action-icon" data-action="favorite" title="${character.favorited ? 'Remove Favorite' : 'Add Favorite'}"> <i class="${character.favorited ? 'fas' : 'far'} fa-star"></i> </div> </div> `; card.innerHTML = ""; $(card).wiki(htmlContent); } else if (State.variables.chars.viewMode === 'compact') { const htmlContent = ` <div class="character-avatar" style="background-color: ${character.color};"> <<HeadImageByID ${character.id}>> </div> <div class="character-name" style="color:${rarityColor}">${character.name}</div> <div class="character-actions"> <div class="action-icon" data-action="favorite" title="${character.favorited ? 'Remove Favorite' : 'Add Favorite'}"> <i class="${character.favorited ? 'fas' : 'far'} fa-star"></i> </div> </div> <div class="character-tags" style="display: flex; flex-wrap: wrap; gap: 3px;"> ${traitsAll} ${tagsAll} </div> `; card.innerHTML = ""; $(card).wiki(htmlContent); } return card; } function selectCharacter(characterId) { if (!characterId) return; if (charMap.get(characterId).traits["PlaneStun"]) { Msg.flash("Plane Stun: Freshly torn through planes, the monster staggers in dizzy disorientation. Unable to act today."); return; }; if ((State.variables.chars.selected?.id == characterId) && setup.isCurrentView("Summon")) { $.wiki(`<<addclass ".leftTogglePanel" "hidden">><<removeclass "#leftactionpanel" "hidden">> <<addclass ".rightTogglePanel" "hidden">><<removeclass "#rightactionpanel" "hidden">><<set $summon.currentsolt = -1 >>`); return; } State.variables.chars.selectedId = characterId; const character = charMap.get(characterId); State.variables.chars.selected = character; if (setup.isCurrentView("Summon")) { EventBus.emit("summon:join", characterId); } $.wiki('<<redo "selected">>'); document.querySelectorAll('.character-card').forEach(card => { card.classList.remove('selected'); }); document.querySelector(`.character-card[data-character-id="${character.id}"]`).classList.add('selected'); document.getElementById('action-target').textContent = character.name; updateTeamSelection(character.id); updateTagCloud(character.id); } function toggleBatchSelection(characterId, cardElement) { if (batchSelectedIds.has(characterId)) { batchSelectedIds.delete(characterId); cardElement.classList.remove('selected'); } else { batchSelectedIds.add(characterId); cardElement.classList.add('selected'); } document.getElementById('action-target').textContent = `Selected: ${batchSelectedIds.size}`; updateTagCloud(); } function toggleFavorite(characterId) { const character = charMap.get(characterId); character.favorited = !character.favorited; charMap.set(character.id, character); const icon = document.querySelector(`.character-card[data-character-id="${character.id}"] .fa-star`); if (icon) { icon.className = character.favorited ? 'fas fa-star' : 'far fa-star'; if (State.variables.chars.viewMode === 'card') { icon.style.color = character.favorited ? 'var(--tr-SS)' : ''; } } const message = character.favorited ? 'Added to Favorites' : 'Removed from Favorites'; Msg.flash(`${character.name} ${message}`); } function handleAction(action) { if (isBatchMode && batchSelectedIds.size === 0) { Msg.flash('Please select at least one character for batch action'); return; } else if (!isBatchMode && !State.variables.chars.selected) { Msg.flash('Please select a character first'); return; } const character = State.variables.chars.selected; switch(action) { case 'rename': if(isBatchMode) { Msg.flash('Batch mode is currently supported only for "Tag", "Relocate" and "Harvest".'); break; }; setup.Popup(`<<textbox "_newname" $chars.selected.name>>`, { title: "New Name", buttons: ['cancel', { text: "Rename", class: "btn-blue", confirm: () => { if (!State.temporary.newname) return; State.variables.chars.selected.name = State.temporary.newname; $.wiki(`<<redo "selected">>`); renderCharacterList(); } },] }); break; case 'stopjob': if(isBatchMode) { Msg.flash('Batch mode is currently supported only for "Tag", "Relocate" and "Harvest".'); break; } $.wiki(`<<if $chars.selected.worktags>> <<set _jobpos = JobSystem.getRef($chars.selected.id)[0]>> <<run JobSystem.fire(_jobpos)>> <<redo>><</if>>`); renderCharacterList(); Msg.flash(`${character.name}'s job was canceled`); break; case 'train': if(isBatchMode) { Msg.flash('Batch mode is currently supported only for "Tag", "Relocate" and "Harvest".'); break; } $.wiki(`<<if $chars.selected.gender == "girl">> <<set $training.trainID.to = $chars.selected.id>><<goto 'Train_UI'>> <<else>> <<run Msg.flash("Currently only supports Female♀️ characters.")>> <</if>>`); break; case 'moveout': if (isBatchMode) { setup.Popup(`Release ${batchSelectedIds.size} selected units into the barony?<br><<= setup.notice_moveout>>`, { title: "Batch Relocate", class:"popup-notice", buttons: ['cancel', { confirm: () => { State.temporary.batchIds = Array.from(batchSelectedIds); $.wiki(`<<for _id range _batchIds>> <<set _c = $chars.dataMap.get(_id)>> <<if _c.type != "unique" and !_c.worktags>> <<run PopSystem.transfer(_c, "temple", "barony")>> <<DelMonster _c>> <</if>> <</for>><<redo "monsterslist selected">><<set _now = passage()>><<goto _now>>`); } }] }); } else { if (character["type"] == "unique") {Msg.flash(L.nospecial); break;}; if (character["worktags"]) {Msg.flash(L.currentlyworking); break;}; setup.Popup(`<<= setup.notice_moveout>>`, { title: "Relocate", class:"popup-notice", buttons: ['cancel', { confirm: () => { PopSystem.transfer(State.variables.chars.selected, "temple", "barony"); $.wiki(`<<DelMonster $chars.selected>><<redo "monsterslist selected">><<set _now = passage()>><<goto _now>>`); } }] }); } break; case 'harvest': if (isBatchMode) { State.temporary.batchIds = Array.from(batchSelectedIds); $.wiki(`<<for _id range _batchIds>> <<set _c = $chars.dataMap.get(_id)>> <<HarvestMon _c>> <</for>><<redo "monsterslist selected">><<redo "topbar">>`); Msg.flash(`Batch Harvest completed for ${batchSelectedIds.size} monsters.`); renderCharacterList(); } else { $.wiki(`<<HarvestMon $chars.selected>><<redo "monsterslist selected">><<redo "topbar">>`); renderCharacterList(); } break; case 'equip': if(isBatchMode) { Msg.flash('Batch mode is currently supported only for "Tag", "Relocate" and "Harvest".'); break; } Msg.flash(`Change equipment for ${character.name}`); break; case 'skill': if(isBatchMode) { Msg.flash('Batch mode is currently supported only for "Tag", "Relocate" and "Harvest".'); break; } Msg.flash(`Select skills for ${character.name}`); break; case 'feed': if(isBatchMode) { Msg.flash('Batch mode is currently supported only for "Tag", "Relocate" and "Harvest".'); break; } Msg.flash(`Use potion for ${character.name}`); break; case 'summon': if(isBatchMode) { Msg.flash('Batch mode is currently supported only for "Tag", "Relocate" and "Harvest".'); break; } const tagManagementFl = document.getElementById('tag-management'); const teamSelectionFl = document.getElementById('team-selection'); const summonManagementFl = document.getElementById('summon-management'); if (summonManagementFl.style.display === 'block') { summonManagementFl.style.display = 'none'; } else { summonManagementFl.style.display = 'block'; teamSelectionFl.style.display = 'none'; tagManagementFl.style.display = 'none'; } break; case 'tag': const tagManagement = document.getElementById('tag-management'); const teamSelection = document.getElementById('team-selection'); if (tagManagement.style.display === 'block') { tagManagement.style.display = 'none'; } else { tagManagement.style.display = 'block'; teamSelection.style.display = 'none'; } break; case 'team': if(isBatchMode) { Msg.flash('Batch mode is currently supported only for "Tag", "Relocate" and "Harvest".'); break; } const teamSelectionEl = document.getElementById('team-selection'); const tagManagementEl = document.getElementById('tag-management'); if (teamSelectionEl.style.display === 'block') { teamSelectionEl.style.display = 'none'; } else { teamSelectionEl.style.display = 'block'; tagManagementEl.style.display = 'none'; } break; } } function addNewTag() { const tagInput = document.getElementById('tag-input'); const newTag = tagInput.value.trim(); if (!newTag) { Msg.flash('Please enter tag content'); return; } if (availableTags.includes(newTag)) { Msg.flash('Tag already exists'); return; } availableTags.push(newTag); const tagsContainer = document.getElementById('tags-container'); const tagElement = document.createElement('div'); tagElement.className = 'tag-item'; tagElement.textContent = newTag; tagElement.dataset.tag = newTag; tagsContainer.appendChild(tagElement); const tagCloudList = document.getElementById('tag-cloud-list'); const tagCloudItem = document.createElement('div'); tagCloudItem.className = 'tag-cloud-item'; tagCloudItem.textContent = newTag; tagCloudItem.dataset.tag = newTag; tagCloudList.appendChild(tagCloudItem); tagElement.addEventListener('click', function() { const tagName = this.dataset.tag; this.classList.toggle('active'); const activeTags = Array.from(document.querySelectorAll('.tag-item.active')) .map(tag => tag.dataset.tag); State.variables.chars.filters.tags = activeTags; applyFilters(); }); tagInput.value = ''; Msg.flash(`Tag "${newTag}" added`); } function toggleCharacterTag(tag) { if (isBatchMode) { if (batchSelectedIds.size === 0) { Msg.flash('Please select at least one character'); return; } let charsWithTag = 0; for (let id of batchSelectedIds) { if (charMap.get(id).tags.includes(tag)) { charsWithTag++; } } const isRemoving = (charsWithTag === batchSelectedIds.size); let modifiedCount = 0; for (let id of batchSelectedIds) { const char = charMap.get(id); const tagIndex = char.tags.indexOf(tag); if (isRemoving && tagIndex !== -1) { char.tags.splice(tagIndex, 1); charMap.set(id, char); modifiedCount++; } else if (!isRemoving && tagIndex === -1) { char.tags.push(tag); charMap.set(id, char); modifiedCount++; } } if (isRemoving) { Msg.flash(`Removed tag [${tag}] from ${modifiedCount} monsters.`); } else { Msg.flash(`Added tag [${tag}] to ${modifiedCount} monsters.`); } updateTagCloud(); renderCharacterList(); } else { if (!State.variables.chars.selected) return; const character = State.variables.chars.selected; const tagIndex = character.tags.indexOf(tag); if (tagIndex === -1) { character.tags.push(tag); Msg.flash(`Added tag to ${character.name}: ${tag}`); } else { character.tags.splice(tagIndex, 1); Msg.flash(`Removed tag from ${character.name}: ${tag}`); } charMap.set(character.id, character); updateTagCloud(character.id); renderCharacterList(); } } function updateTagCloud(characterId) { const tagCloudItems = document.querySelectorAll('.tag-cloud-item'); if (isBatchMode) { if (batchSelectedIds.size === 0) { tagCloudItems.forEach(item => item.classList.remove('selected')); return; } tagCloudItems.forEach(item => { const tag = item.dataset.tag; let allHaveIt = true; for (let id of batchSelectedIds) { if (!charMap.get(id).tags.includes(tag)) { allHaveIt = false; break; } } if (allHaveIt) { item.classList.add('selected'); } else { item.classList.remove('selected'); } }); } else { if (!characterId) return; const character = charMap.get(characterId); if (!character) return; tagCloudItems.forEach(item => { const tag = item.dataset.tag; if (character.tags.includes(tag)) { item.classList.add('selected'); } else { item.classList.remove('selected'); } }); } } function updateTeamSelection(characterId) { const character = charMap.get(characterId); const teamRadios = document.querySelectorAll('input[name="team"]'); teamRadios.forEach(radio => { radio.checked = radio.value === character.team; }); } function assignCharacterToTeam(team) { if (!State.variables.chars.selected) return; const character = State.variables.chars.selected; character.team = team; charMap.set(character.id, character); Msg.flash(`${character.name} assigned to ${team}`); } function updateCounts() { const allCharacters = charList; document.getElementById('total-count').textContent = allCharacters.length; const classCounts = { feline: 0, }; } function updateTagCloudUI() { const tagsContainer = document.getElementById('tags-container'); tagsContainer.innerHTML = ''; availableTags.forEach(tag => { const tagElement = document.createElement('div'); tagElement.className = 'tag-item'; tagElement.textContent = tag; tagElement.dataset.tag = tag; tagsContainer.appendChild(tagElement); }); const tagCloudList = document.getElementById('tag-cloud-list'); tagCloudList.innerHTML = ''; availableTags.forEach(tag => { const tagElement = document.createElement('div'); tagElement.className = 'tag-cloud-item'; tagElement.textContent = tag; tagElement.dataset.tag = tag; tagCloudList.appendChild(tagElement); }); bindTagClickEvents(); } function bindTagClickEvents() { document.querySelectorAll('.tag-item').forEach(tag => { tag.addEventListener('click', function() { const tagName = this.dataset.tag; this.classList.toggle('active'); const activeTags = Array.from(document.querySelectorAll('.tag-item.active')) .map(tag => tag.dataset.tag); State.variables.chars.filters.tags = activeTags; applyFilters(); }); }); } <</script>> <</done>> <</widget>>
<<widget "ShowPortrait">> <<do tag "selected shoplist">> <<if _args[0]>> <<set _cssid = _args[1] ? _args[1] : "portrait-container" >> <<set _offcloth = _args[2] ?? "false" >> <div class="char-cg-box" @id="_cssid"></div> <<capture _args[0] _cssid>> <<done>> <<dynamicChar _args[0] _cssid `{ bodyMorphs: { legWidth: 1 }, showClothes: _offcloth }` >> <</done>> <</capture>> <</if>> <</do>> <</widget>> <<widget "ShowPortraitByCGid">> <<do tag "selected shoplist">> <<if _args[0]>> <div class="monsterinfoleftpic" id="portrait-container"></div> <<set _showimg = { src: "" }; >> <<set _rootFontSize = parseFloat(getComputedStyle(document.documentElement).fontSize); >> <<set _showpreg = _args[0].preg > 0 ? ['breast', 'belly'] : ['breast'] >> <<set _showimg.src = setup.getPortraitPath(_args[0], _args[1])>> <<set _viewheight = Math.clamp((_args[0].height * 3.75) , 256 , document.body.offsetHeight * 0.9)>> <<timed 0.1s>> <<run AnimeShow.dynamicPortrait( _showimg.src, _viewheight, _showpreg, "portrait-container" );>> <</timed>> <</if>> <</do>> <</widget>>
<<run $('#portrait-container').empty()>>
<<run AudioBus.playPlaylist('patreon');>> <div class="patron-container"> <div class="header"> <div class="lord-title">The Patron's Treasury</div> <div class="main-title">Lord's Sanctum</div> <div class="subtitle"> Your noble generosity ensures the prosperity of the Barony. By your decree, these ancient boons are unlocked to aid in your conquest. </div> </div> <div class="main-content"> <div class="left-panel"> <div class="verification-panel"> <div class="panel-title"> <i class="fas fa-shield-alt"></i> <span>Lord's Seal Verification</span> </div> <input type="text" class="code-input" id="patreon-code" placeholder="Enter Patreon Supporter Code..."> <button class="verify-btn" id="verify-btn"> <i class="fas fa-check-circle"></i> Verify Noble Status </button> </div> <div class="rewards-panel"> <div class="panel-title"> <i class="fas fa-crown"></i> <span>Royal Rewards</span> </div> <div class="rewards-list" id="rewards-list"> <div class="reward-item locked"> <div class="reward-icon"> <i class="fas fa-chess-king"></i> </div> <div class="reward-info"> <div class="reward-name">Royal Treasury Grant</div> <div class="reward-desc">Infinite Resource Access</div> </div> <button data-reward-id="0" class="claim-btn" disabled> Locked </button> </div> <div class="reward-item locked"> <div class="reward-icon"> <i class="fas fa-bolt"></i> </div> <div class="reward-info"> <div class="reward-name">Aetheric Surge</div> <div class="reward-desc">+500 Max Energy Capacity</div> </div> <button data-reward-id="1" class="claim-btn" disabled> Locked </button> </div> <div class="reward-item locked"> <div class="reward-icon"> <i class="fas fa-scroll"></i> </div> <div class="reward-info"> <div class="reward-name">Royal Edict of Summoning</div> <div class="reward-desc">Cards drawn increased by +1.</div> </div> <button data-reward-id="2" class="claim-btn" disabled> Locked </button> </div> <div class="reward-item locked"> <div class="reward-icon"> <i class="fas fa-cube"></i> </div> <div class="reward-info"> <div class="reward-name">Ancient Artifact Core</div> <div class="reward-desc">+1 Advanced Infuser Core</div> </div> <button data-reward-id="3" class="claim-btn" disabled> Locked </button> </div> <div class="reward-item locked"> <div class="reward-icon"> <i class="fas fa-book-reader"></i> </div> <div class="reward-info"> <div class="reward-name">Governance Mastery</div> <div class="reward-desc">+200 Stewardship Points</div> </div> <button data-reward-id="4" class="claim-btn" disabled> Locked </button> </div> <div class="reward-item locked"> <div class="reward-icon"> <i class="fa-solid fa-flask"></i> </div> <div class="reward-info"> <div class="reward-name">Master's Gift</div> <div class="reward-desc">+2 Omni-Boost Potion</div> </div> <button data-reward-id="5" class="claim-btn" disabled> Locked </button> </div> <div class="reward-item locked"> <div class="reward-icon"> <i class="fa-solid fa-flask"></i> </div> <div class="reward-info"> <div class="reward-name">Potential Elixir</div> <div class="reward-desc">+5 Potential Elixir</div> </div> <button data-reward-id="6" class="claim-btn" disabled> Locked </button> </div> </div> </div> </div> <div class="right-panel"> <div class="console-header"> <div class="lord-welcome" id="welcome-message"> Awaiting Verification... </div> </div> <div class="resources-grid" id="resources-grid"> <div class="resource-card"> <div class="resource-icon"> <i class="fas fa-coins"></i> </div> <div class="resource-info"> <div class="resource-name">Gold</div> <div class="resource-amount">+1000</div> </div> <button data-resource-id="gold" class="resource-btn" disabled> Acquire </button> </div> <div class="resource-card"> <div class="resource-icon"> <i class="fa-solid fa-bread-slice"></i> </div> <div class="resource-info"> <div class="resource-name">Food</div> <div class="resource-amount">+100</div> </div> <button data-resource-id="food" class="resource-btn" disabled> Acquire </button> </div> <div class="resource-card"> <div class="resource-icon"> <i class="fa-solid fa-tree"></i> </div> <div class="resource-info"> <div class="resource-name">Wood</div> <div class="resource-amount">+50</div> </div> <button data-resource-id="wood" class="resource-btn" disabled> Acquire </button> </div> <div class="resource-card"> <div class="resource-icon"> <i class="fa-solid fa-cubes-stacked"></i> </div> <div class="resource-info"> <div class="resource-name">Stone</div> <div class="resource-amount">+50</div> </div> <button data-resource-id="stone" class="resource-btn" disabled> Acquire </button> </div> <div class="resource-card"> <div class="resource-icon"> <i class="fa-solid fa-diamond"></i> </div> <div class="resource-info"> <div class="resource-name">Ore</div> <div class="resource-amount">+50</div> </div> <button data-resource-id="ore" class="resource-btn" disabled> Acquire </button> </div> </div> </div> </div> <div class="footer"> <p> Exclusive tribute for our Noble Patrons.<br> Your support fuels the Barony's development.<br> Continue your patronage on <a href="https:\\patreon.com/AxiomNova" class="patreon-link" target="_blank">Patreon</a>. </p> </div> </div> <div class="status-message" id="status-message"></div> <<done>> <<script>> const checkResult = setup.PatreonSystem.verifySavedHash(); if (checkResult.status === 'valid') { showVerify(); } else if (checkResult.status === 'expired') { setTimeout(() => { showStatus('Your previous Royal Seal has expired with the new update. Please claim the latest edict!', 'warning'); showPatreonGuidance(); }, 500); } bindEvents(); function bindEvents() { document.getElementById('verify-btn').addEventListener('click', onVerifyClick); document.getElementById('patreon-code').addEventListener('keypress', function(e) { if (e.key === 'Enter') onVerifyClick(); }); } const TARGET_HASH = "c3c673d8cc9450ef8fffd6634db20d7937128a24f0f492f7e8959e61ca7479ea"; async function onVerifyClick() { const codeInput = document.getElementById('patreon-code'); const code = codeInput.value.trim(); const verifyBtn = document.getElementById('verify-btn'); verifyBtn.disabled = true; verifyBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Verifying...'; const result = await setup.PatreonSystem.verifyInput(code); if (result.success) { EventBus.emit("code:redeem", { code: true }); $.wiki('<<redo "indexMenu">>'); showVerify(); showStatus('Welcome, noble Lord! Your generosity honors us. Your domain awaits.', 'success'); } else { verifyBtn.disabled = false; verifyBtn.innerHTML = '<i class="fas fa-check-circle"></i> Verify Noble Status'; if (result.reason === "empty") { showStatus('Please present your Royal Seal (Code) first, My Lord.', 'warning'); } else { showStatus('The seal has expired or is invalid. Please visit our Patreon for the latest edict!', 'error'); codeInput.value = ''; codeInput.focus(); showPatreonGuidance(); } } } function showVerify() { const codeInput = document.getElementById('patreon-code'); codeInput.value = '✓ ROYAL SEAL ACCEPTED'; codeInput.disabled = true; codeInput.style.color = 'var(--accent-gold)'; codeInput.style.borderColor = 'var(--accent-gold)'; const verifyBtn = document.getElementById('verify-btn'); verifyBtn.innerHTML = '<i class="fas fa-crown"></i> Verified Noble'; verifyBtn.disabled = true; showVerifiedState(); showStatus('Welcome, noble Lord! Your domain awaits.', 'success'); } function showVerifiedState() { document.getElementById('welcome-message').textContent = `Welcome, My Lord! Command your resources.`; const claimButtons = document.querySelectorAll('.claim-btn'); claimButtons.forEach(btn => { const rewardindex = btn.dataset.rewardId; const isindex = State.variables.gamestatus.reward[rewardindex]; if (isindex || rewardindex == 0) { btn.textContent = 'Claimed'; btn.disabled = true; btn.style.background = '#71797E'; } else { btn.textContent = 'Claim'; btn.disabled = false; btn.addEventListener('click', function() { claimReward(this); });} }); const resourceButtons = document.querySelectorAll('.resource-btn'); resourceButtons.forEach(btn => { btn.disabled = false; btn.addEventListener('click', function() { acquireResource(this); }); }); const rewardItems = document.querySelectorAll('.reward-item'); rewardItems.forEach(item => { item.classList.remove('locked'); }); } function claimReward(button) { if (State.variables.gamestatus.stage < 1) { showStatus(`My load, Please claim your rewards after the game launches.`, 'warning'); return; }; const rewardItem = button.closest('.reward-item'); const rewardindex = button.dataset.rewardId; State.variables.gamestatus.reward[rewardindex] = true; const rewardName = rewardItem.querySelector('.reward-name').textContent; switch (rewardindex) { case "1": State.variables.manage.energy.current += 500; State.variables.manage.energy.base += 500; break; case "2": State.variables.manage.cardSelect += 1; break; case "3": State.variables.storage.pickup("Advanced_Infuser_Core", 1); break; case "4": State.variables.tech.techPoint.stewardship += 200; break; case "5": State.variables.storage.pickup("allPotion", 2); break; case "6": State.variables.storage.pickup("potentialPotion-10", 5); break; default: break; } $.wiki(`<<redo "topbar">>`) ; button.textContent = 'Claimed'; button.disabled = true; button.style.background = '#71797E'; showStatus(`${rewardName} has been added to your treasury!`, 'success'); } function acquireResource(button) { if (State.variables.gamestatus.stage < 1) { showStatus(`My load, Please claim your rewards after the game launches.`, 'warning'); return; }; const resourceCard = button.closest('.resource-card'); const resource = button.dataset.resourceId; switch (resource) { case "gold": State.variables.storage.pickup("gold", 1000); break; case "food": State.variables.storage.pickup("food", 100); break; case "wood": State.variables.storage.pickup("wood", 50); break; case "stone": State.variables.storage.pickup("stone", 50); break; case "ore": State.variables.storage.pickup("ore", 50); break; default: break; } const resourceName = resourceCard.querySelector('.resource-name').textContent; showStatus(`${resourceName} added to your stores!`, 'success'); $.wiki(`<<redo "topbar">>`) ; } function showPatreonGuidance() { let guide = document.getElementById('patreon-guide-msg'); if (!guide) { guide = document.createElement('div'); guide.id = 'patreon-guide-msg'; guide.style.color = '#C0C0C0'; guide.style.fontSize = '0.85rem'; guide.style.marginTop = '10px'; guide.style.textAlign = 'center'; guide.innerHTML = `Need the current code? <a href="https:\\www.patreon.com/AxiomNova" target="_blank" style="color: var(--accent-gold); text-decoration: underline;">Check our latest update post here.</a>`; document.querySelector('.verification-panel').appendChild(guide); } } function showStatus(message, type) { const statusEl = document.getElementById('status-message'); statusEl.textContent = message; statusEl.style.display = 'block'; if (type === 'error') { statusEl.style.borderColor = 'var(--accent-red)'; } else if (type === 'warning') { statusEl.style.borderColor = 'var(--accent-gold)'; } else { statusEl.style.borderColor = '#228B22'; } setTimeout(() => { statusEl.style.display = 'none'; }, 3000); } <</script>><</done>>
<<PopTable "temple">> <br> <<PopTable "barony">>
<<script>> $(document).ready(function() { $('.quests-tab').on('click', function() { $('.quests-tab').removeClass('quests-active'); $(this).addClass('quests-active'); $('.quests-task-list').removeClass('quests-active'); $('#' + $(this).data('tab')).addClass('quests-active'); }); window.renderQuestsUI = function() { $('#tab-all, #tab-mainline, #tab-side, #tab-bounty, #tab-completed, #tab-failed').empty(); const treeData = Qum.pack(); treeData.forEach(task => { let targetTabId = ''; if (task.status === 'completed') { targetTabId = '#tab-completed'; } else if (task.status === 'failed') { targetTabId = '#tab-failed'; } else { if (task.type === 'mainline') { targetTabId = '#tab-mainline'; } else if (task.type === 'bounty' || task.expiryType === 'daily' || task.expiryType === 'weekly') { targetTabId = '#tab-bounty'; } else { targetTabId = '#tab-side'; } if (task.status === 'active') { const allCardHtml = renderSingleCard(task, false); $('#tab-all').append(allCardHtml); } } if (targetTabId) { const parentCardHtml = renderSingleCard(task, false); $(targetTabId).append(parentCardHtml); } }); $('.quests-task-card').off('click').on('click', function(e) { if ($(e.target).closest('.quests-btn, a, button').length) return; e.stopPropagation(); $(this).toggleClass('expanded'); }); $('.quests-btn.quests-btn-enabled').off('click').on('click', function(e) { e.stopPropagation(); const taskId = $(this).data('task-id'); if (Qum.finish(taskId)) { $.wiki("<<redo 'topbar'>>"); window.renderQuestsUI(); } }); }; function renderSingleCard(task, isSubTask) { const isMainline = task.type === 'mainline'; const isMain = task.type === 'main'; const isSub = task.type === 'sub'; let cardClass = isMainline ? 'quests-mainline-task' : isMain ? 'quests-main-task' : 'quests-sub-task'; if (task.status === 'completed') cardClass += ' quests-completed'; if (task.status === 'failed') cardClass += ' quests-failed'; const statusText = { active: 'In Progress', completed: 'Completed', failed: 'Failed' }[task.status]; let btnHtml = ''; if (task.status === 'active') { const btnClass = task.canComplete ? 'quests-btn-enabled' : ''; const disAttr = task.canComplete ? '' : 'disabled'; btnHtml = `<button class="quests-btn ${btnClass}" data-task-id="${task.id}" ${disAttr}>Complete</button>`; } let progressHtml = ''; if (task.status === 'active' && task.progress) { task.progress.forEach(p => { progressHtml += ` <div class="quests-objective-bar-container"> <div class="quests-objective-bar-label"> <span>${p.desc}</span> <span>${p.current} / ${p.target} (${p.percent}%)</span> </div> <div class="quests-objective-bar-bg"> <div class="quests-objective-bar-fill-self" style="width: ${p.percent}%;"></div> </div> </div> `; }); } let subTasksTrackerHtml = ''; if (!isSubTask && task.subObjects && task.subObjects.length > 0) { subTasksTrackerHtml += `<div class="quests-collapsed-sub-tracker">`; task.subObjects.forEach(sub => { let bulletColor = '#3b82f6'; let subStateText = 'In Progress'; if (sub.status === 'completed') { bulletColor = '#10b981'; subStateText = 'Completed'; } else if (sub.status === 'failed') { bulletColor = '#ef4444'; subStateText = 'Failed'; } else if (sub.canComplete) { bulletColor = '#ffd366'; subStateText = 'Ready to Claim!'; } else if (sub.progress && sub.progress.length > 0) { const p = sub.progress[0]; subStateText = `${p.current} / ${p.target}`; } const subTitleStyle = sub.status === 'completed' ? 'style="text-decoration: line-through; opacity: 0.5;"' : ''; subTasksTrackerHtml += ` <div class="quests-sub-tracker-row"> <span> <span class="quests-subtask-bullet" style="background: ${bulletColor};"></span> <span class="quests-sub-tracker-title" ${subTitleStyle}>${sub.title}</span> </span> <span class="quests-sub-tracker-status" style="color: ${bulletColor};">${subStateText}</span> </div> `; }); subTasksTrackerHtml += `</div>`; } let subTasksGroupHtml = ''; if (!isSubTask && task.subObjects && task.subObjects.length > 0) { subTasksGroupHtml += `<div class="quests-nested-container">`; subTasksGroupHtml += `<div class="quests-nested-label">Detailed objectives (Click to expand)</div>`; task.subObjects.forEach(sub => { subTasksGroupHtml += renderSingleCard(sub, true); }); subTasksGroupHtml += `</div>`; } const evaluatedDesc = typeof task.desc === "function" ? task.desc() : task.desc; const tipsHtml = task.tips ? `<div class="quests-task-desc" style="color: #888; font-style: italic;">${task.tips}</div>` : ''; const reqHtml = task.conditionDesc ? `<div class="quests-task-info">Requirements: ${task.conditionDesc}</div>` : ''; const rewHtml = task.rewardDesc ? `<div class="quests-task-info" style="color: #10b981; margin-top: 0.4rem;">Rewards: ${task.rewardDesc}</div>` : ''; return ` <div class="quests-task-card ${cardClass}" data-task-id="${task.id}"> <div class="quests-task-title-row"> <div class="quests-task-title">${task.title}</div> <div class="quests-task-status-btn"> <div class="quests-task-status quests-${task.status}">${statusText}</div> ${btnHtml} </div> </div> ${progressHtml} ${subTasksTrackerHtml} <div class="quests-task-content"> <div class="quests-task-desc">${evaluatedDesc}</div> ${tipsHtml} ${reqHtml} ${rewHtml} ${subTasksGroupHtml} </div> </div> `; } window.renderQuestsUI(); }); <</script>> <div class="quests-container"> <div class="quests-tabs"> <div class="quests-tab quests-active" data-tab="tab-all">All</div> <div class="quests-tab" data-tab="tab-mainline">Main</div> <div class="quests-tab" data-tab="tab-side">Side</div> <div class="quests-tab" data-tab="tab-bounty">Bounties</div> <div class="quests-tab" data-tab="tab-completed">Completed</div> <div class="quests-tab" data-tab="tab-failed">Failed</div> </div> <div class="quests-task-list quests-active" id="tab-all"></div> <div class="quests-task-list" id="tab-mainline"></div> <div class="quests-task-list" id="tab-side"></div> <div class="quests-task-list" id="tab-bounty"></div> <div class="quests-task-list" id="tab-completed"></div> <div class="quests-task-list" id="tab-failed"></div> </div>
<div class="camp-wrapper"> <div id="camp-dynamic-area"> <<do tag "popsettlement">> <<set _campPop = PopSystem.query({ loc: "settlement" })>> <<set _campData = PopSystem.getHousingTableData("settlement")>> <div class="camp-header"> <div class="camp-title"> <i class="fas fa-campground"></i> Refugee Camp </div> <div class="camp-stats"> Total: <span class="highlight"><<print PopSystem.formatNum(_campPop)>></span> <span style="font-size:0.8em; opacity:0.5">/ ∞</span> </div> </div> <<if _campData.length == 0>> <div class="empty-msg"> <i class="fas fa-wind" style="font-size:2rem; margin-bottom:1rem; display:block;"></i> The settlement is quiet. No refugees are currently waiting. </div> <<else>> <div class="camp-grid"> <<for _entry range _campData>> <<capture _entry>> <<set _raceInfo = setup.bestiaryDB[_entry.id]>> <div class="camp-card"> <div class="card-header"> <div class="race-name"> <<if _raceInfo.icon>>_raceInfo.icon<</if>> <<print _raceInfo.name>> </div> <div class="race-count"><<print PopSystem.formatNum(_entry.count)>></div> </div> <div class="gender-bar"> <span title="Females" style="color:#f687b3"><i class="fas fa-venus"></i><<print PopSystem.formatNum(_entry.genders.girl)>></span> <span title="Males" style="color:#63b3ed"><i class="fas fa-mars"></i><<print PopSystem.formatNum(_entry.genders.boy)>></span> <<if _entry.genders.futa > 0>> <span title="Futa" style="color:#a78bfa"><i class="fas fa-transgender"></i><<print PopSystem.formatNum(_entry.genders.futa)>></span> <</if>> </div> <div class="action-group"> <div class="btn-row"> <<link '<div class="camp-btn btn-accept"><i class="fas fa-plus"></i> Add 10</div>'>> <<run PopSystem.batchTransfer(_entry.id, 10, 'settlement', 'barony');>> <<redo "popsettlement">> <</link>> <<link '<div class="camp-btn btn-accept"><i class="fas fa-check-double"></i> Add All</div>'>> <<run PopSystem.batchTransfer(_entry.id, 'all', 'settlement', 'barony');>> <<redo "popsettlement">> <</link>> </div> <div class="btn-row"> <<link '<div class="camp-btn btn-expel"><i class="fas fa-ban"></i> Expel 10</div>'>> <<run PopSystem.batchExpel(_entry.id, 10, 'settlement');>> <<redo "popsettlement">> <</link>> <<link '<div class="camp-btn btn-expel"><i class="fas fa-times-circle"></i> Expel All</div>'>> <<run PopSystem.batchExpel(_entry.id, 'all', 'settlement');>> <<redo "popsettlement">> <</link>> </div> </div> </div> <</capture>> <</for>> </div> <</if>><</do>> </div> </div>
<<set $chars.selected = null>> <div class="UIbox slave-UIbox"> <<do tag "slaver">> <<MapList $slave.slaveList $slave.slaveMap>> <</do>> <div class="slave-display"> <<do tag "selected">> <<if $chars.selected>> <<ShowPortrait $chars.selected>> <</if>> <div class="slave-buy ui-row-center"> <div class="op" style="justify-items: center;"> <<link '<<showframe "icon64" "buy" 4>>'>> <<if $chars.selected>> <<run setup.Popup(`<<set _slaveBuy = $chars.selected>><<include "Slaver_PurchaseOptions">>`);>> <</if>> <</link>> <div id="slave-price">Buy: <<if $chars.selected>><<= $chars.selected.price>><<else>>-<</if>>g</div> </div> <div class="op" style="justify-items: center;"> <<set _refreshcost = setup.getRefreshCost() >> <<link '<<showframe "icon64" "refresh" 4>>'>> <<if $storage.count('gold') > _refreshcost>> <<drop $storage "gold" _refreshcost>> <<set $chars.selected = null>> <<set $slave.slaveList = [] >> <<run $slave.slaveMap.clear()>> <<NewSlave>> <<set $slave.refresh += 1 >> <<redo "selected topbar slaver">> <<run Msg.notify("Refresh Slaves");>> <<else>> <<run Msg.flash(L.moregold)>> <</if>> <</link>> <div id="slave-price">Refresh: <<= _refreshcost>> g</div> </div> </div> <</do>> </div> <div class="slave-infolist"> <div class="slave-infocard"> <<do tag "selected">> <<if $chars.selected>> <<Infolist $chars.selected>> <</if>> <</do>> </div> </div> </div> <<set _FacHelp = "CodexEntry_Building_SlaverCaravan" >>
<div class="purchase-modal"> <div class="options-column"> <div class="header"> <h2>Contract of Ownership</h2> <p>Subject No. _slaveBuy.id</p> </div> <div class="rename-section"> <label class="rename-label">Designate Identity</label> <div class="input-wrapper"> <span>✒️</span> <<textbox "_slaveBuy.name" _slaveBuy.name>> </div> </div> <div class="options-list"> <div class="option-card premium" id="opt-premium" data-type="premium" data-cost="50"> <div class="badge">RECOMMENDED</div> <div class="opt-header"> <span class="opt-title">Bestow Dignity & Comfort</span> <span class="opt-price">+50 G</span> </div> <div class="opt-desc"> Their trembling betrays not just a chill, but the shame of filth and the seething anger of captivity. A set of fresh linens and a reassuring smile might be the first mercy they have known in a long time. </div> <div class="opt-effect"> ✦ Discards <b>[Filthy Rags]</b> & Equips <b>[Clean Linens]</b><br> ✦ Soothes anger and sparks trust: <b>[+50 Mood]</b> </div> </div> <div class="option-card selected" id="opt-basic" data-type="basic" data-cost="0"> <div class="opt-header"> <span class="opt-title">Acquire As-Is</span> <span class="opt-price">Included</span> </div> <div class="opt-desc"> Complete the transaction immediately. </div> <div class="opt-effect"> ✦ Character joins in <b>[Lost Temple]</b> </div> </div> </div> <div class="footer"> <div class="total-price"> Total: <span id="final-price">_slaveBuy.price G</span> </div> <div id="confirm-button-wrapper"> <<button "Finalize Contract">> <<main>> <<if PopSystem.isOvercrowded("temple")>><<run Msg.flash(L.moretemplespace)>><<exit>><</if>> <<set _hasMoney = $storage.count('gold')>> <<set _hasEnergy = $manage.energy.current>> <<if setup.selectedOption === 'premium'>> <<set _payMoney = _slaveBuy.price + 50>> <<set _payEnergy = 0>> <<elseif setup.selectedOption === 'reassure'>> <<set _payMoney = _slaveBuy.price>> <<set _payEnergy = _slaveBuy.price + 100>> <<else>> <<set _payMoney = _slaveBuy.price>> <<set _payEnergy = 0>> <</if>> <<if _payMoney > _hasMoney>><<run Msg.flash(L.moregold)>><<exit>><</if>> <<if _payEnergy > _hasEnergy>><<run Msg.flash(L.moreenergy)>><<exit>><</if>> <<drop $storage "gold" _payMoney>> <<set _hasEnergy -= _payEnergy >> <<set _atlasGender = _slaveBuy.gender === "boy" ? "boy" : "girl">> <<if setup.selectedOption === 'premium'>> <<set _slaveBuy.atlas.parts["clothesSet"] = { frame: AvatarSystem.AtlasUtils.getSmartKey(`cloth_${_atlasGender}_towner`), atlas: `cloth_${_atlasGender}_towner` } >> <<set _slaveBuy.mood.vl += 50>> <<run setup.refreshPortrait(_slaveBuy.id)>> <</if>> <<AddMonster _slaveBuy>> <<run EventBus.emit("slave:buy", { charId: _slaveBuy.id, day: State.variables.times.total });>> <<run $slave.slaveList.deleteAll($chars.selected.id)>> <<set $chars.selected = null>> <<run setup.Popup.close()>> <</main>> <<redo "slaver monsterslist selected topbar">> <</button>> </div> </div> </div> </div> <<done>> <<script>> setup.selectedOption = 'basic'; const price = State.temporary.slaveBuy.price; const priceDisplay = $(".purchase-modal").find('#final-price'); const nameInput = $(".purchase-modal").find('input[type="text"]'); const nameDisplay = $(".purchase-modal").find('#live-name-display'); nameInput.on('input', function() { let val = $(this).val(); if(!val) val = "..."; nameDisplay.text(val); }); $(".purchase-modal").find('.option-card').on('click', function() { const type = $(this).data('type'); const addedCost = $(this).data('cost'); $(".purchase-modal").find('.option-card').removeClass('selected'); $(this).addClass('selected'); setup.selectedOption = type; priceDisplay.text((price + addedCost) + " G"); }); $(".purchase-modal").find('#opt-premium').hover( function() { if (setup.selectedOption !== 'premium') updateVisuals(true); }, function() { if (setup.selectedOption !== 'premium') updateVisuals(false); } ); function updateVisuals(isPremium) { } <</script>> <</done>>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<includeplus "Slaver">> </div> <<include "UI_RightBar">> </div> </div>
<style> .disclaimer-overlay { position: fixed; top: -100px; left: 0; width: 100%; height: 100%; background: rgba(10, 10, 10, 0.95); display: flex; justify-content: center; align-items: center; z-index: 9999; } .disclaimer-box { background: #18181b; border: 1px solid #27272a; border-top: 4px solid #ef4444; padding: 2.5rem; width: 100%; max-width: 800px; border-radius: 12px; box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.7); color: #d4d4d8; } .disclaimer-box h2 { color: #f43f5e; margin-top: 0; margin-bottom: 1.5rem; font-size: 1.35rem; text-align: center; letter-spacing: 0.05em; } .disclaimer-text { font-size: 0.92rem; line-height: 1.6; margin-bottom: 2rem; color: #a1a1aa; } .disclaimer-text p { margin-bottom: 1rem; } .disclaimer-actions { text-align: center; } .disclaimer-box button { background: #e11d48; color: #ffffff; border: none; padding: 0.75rem 2rem; font-size: 0.95rem; font-weight: 600; border-radius: 6px; cursor: pointer; transition: background 0.2s, transform 0.1s; box-shadow: 0 4px 12px rgba(225, 29, 72, 0.3); } .disclaimer-box button:hover { background: #be123c; transform: translateY(-1px); } .disclaimer-box button:active { transform: translateY(0); } </style> <<run UIBar.hide().stow();>> <div class="disclaimer-overlay"> <div class="disclaimer-box"> <h2>⚠️ AGE RESTRICTION & CONTENT WARNING ⚠️</h2> <div class="disclaimer-text"> <p>This software contains explicit adult content, including mature themes, sexual acts, graphic violence, and gore, which may not be suitable for minors or sensitive audiences.</p> <p>By entering, you formally acknowledge and agree that you are at least 18 years of age (or the legal age of majority in your jurisdiction) and that you choose to access this material of your own free will.</p> <p>All characters depicted in any adult or sexual contexts are strictly fictional and are legally categorized as 18 years of age or older. This is a work of fiction; any resemblance to actual persons, living or dead, or real-world events is purely coincidental.</p> <p>If you do not meet these requirements, find such material offensive, or are prohibited by local laws from viewing adult content, please exit immediately.</p> </div> <div class="disclaimer-actions"> <<button "I am 18+, I understand, and I wish to enter">><<goto 'Welcome'>><</button>> </div> </div> </div>
<<script>> window.sortItems = function(criteria) { let sort = criteria.dataset.sort; document.querySelectorAll('.sort-btn').forEach(btn => { btn.classList.toggle('active', btn.dataset.sort === sort); }); }; <</script>> <<unset $choiceitems>> <div class="storage-panel"> <<do tag "sort-left">> <div class="nav-list"> <<link '<div class="nav-tab" id="all"><<= "All">></div>'>> <<ToggleTags "nav-tab" "all">> <<set $currentFilter = "All">><<redo "itemlist">> <</link>> <<for _type range setup.itemTypes>> <<capture _type>> <<link '<div class="nav-tab" @id="_type"><<= setup.langbank[_type][$lang]>></div>'>> <<ToggleTags "nav-tab" _type>> <<set $currentFilter = _type>> <<redo "itemlist">> <</link>> <</capture>> <</for>> </div> <</do>> <div class="content-panel"> <div class="sort-bar"> <div class="sort-list"> <div class="sort-btn" data-sort='lv' onclick="sortItems(this)"><<link "🌟">><<set $sortBy = "lv" >><<redo "itemlist">><</link>></div> <div class="sort-btn" data-sort='num' onclick="sortItems(this)"><<link "📦">><<set $sortBy = "qty" >><<redo "itemlist">><</link>></div> <div class="sort-btn" data-sort='price' onclick="sortItems(this)"><<link "💰">><<set $sortBy = "price" >><<redo "itemlist">><</link>></div> <div class="sort-btn" data-sort='name' onclick="sortItems(this)"><<link "✏️">><<set $sortBy = "name" >><<redo "itemlist">><</link>></div> <div class="sort-btn" data-sort='order' onclick="sortItems(this)"><<link "🕐">><<set $sortBy = "order" >><<redo "itemlist">><</link>></div> </div> </div> <<set $currentFilter = "All">> <<set $sortBy = "">> <<do tag "itemlist">> <<set _itemsindexs = Array.from($storage.list.keys());>> <<if $currentFilter == "All">> <<set _filteredindexs = _itemsindexs>> <<else>> <<set _filteredindexs = _itemsindexs.filter(index => getiteminfo($storage.list[index],"type") == $currentFilter)>> <</if>> <<if $sortBy == "name">> <<set _sortedindexs = _filteredindexs.sort((a, b) => getiteminfo($storage.list[a],"name").localeCompare(getiteminfo($storage.list[b],"name")))>> <<elseif $sortBy == "namedown">> <<set _sortedindexs = _filteredindexs.sort((a, b) => getiteminfo($storage.list[b],"name").localeCompare(getiteminfo($storage.list[a],"name")))>> <<elseif $sortBy == "lv">> <<set _sortedindexs = _filteredindexs.sort((a, b) => getiteminfo($storage.list[b],"lv") - (getiteminfo($storage.list[a],"lv")))>> <<elseif $sortBy == "qty">> <<set _sortedindexs = _filteredindexs.sort((a, b) => $storage.count($storage.list[b]) - $storage.count($storage.list[a]))>> <<elseif $sortBy == "price">> <<set _sortedindexs = _filteredindexs.sort((a, b) => getiteminfo($storage.list[b],"price") - (getiteminfo($storage.list[a],"price")))>> <<elseif $sortBy == "order">> <<set _sortedindexs = _filteredindexs>> <<else>> <<set _sortedindexs = _filteredindexs>> <</if>> <div class="card-list p-2 bg-body"> <<for _itemindex range _sortedindexs>> <<set _item = $storage.list[_itemindex] >> <<capture _item _itemindex>> <<set _itemType = getiteminfo(_item,"type") >> <<set _itemId = getiteminfo(_item,"id") >> <<set _itemStar = getiteminfo(_item,"lv") >> <<set _itemSlot = ` <div class="item-slot" @data-category="_itemType" @id="_itemindex"> <div style="align-self: center;"> <<showframe _itemType _itemId 6.5>> </div> <div class="item-glow" style="bottom: 0; position: absolute;"> x<<= $storage.count(_item)>> </div> <div class="item-quickinfo"> <span class="item-rarity-star"> <<NumToStars _itemStar>> </span> </div> </div>` >> <<link _itemSlot>> <<ToggleTags "item-slot" _itemindex>> <<set $choiceitems = _item >><<redo "desc">> <</link>> <</capture>> <</for>> </div> <</do>> </div> <<do tag "desc">> <div class="item-detail-container"> <<if def $choiceitems>> <<ItemCardShow $choiceitems 16>> <</if>> <div class="storage-action-panel"> <<if def $choiceitems && (getiteminfo($choiceitems,"type") == "harvest" || getiteminfo($choiceitems,"type") == "alchemy")>> <<button `L.feed`>> <<if Flag('enblefeed')>> <<set $manage.feed.feedCp.itemIDLv = $choiceitems >> <<FeedItem $manage.feed.feedCp.charID $manage.feed.feedCp.itemIDLv>> <<SetFlag 'enblefeed' false >> <<run Dialog.close();>> <<else>> <<run Msg.flash(L.summonHint)>> <</if>> <</button>><span></span> <</if>> </div> <div class="storage-delbtns"> <<if (def $choiceitems) && (getiteminfo($choiceitems,"type") != "special")>> <<button "🗑️-1">> <<drop $storage $choiceitems 1>> <<redo "itemlist">> <</button>> <<button "🗑️-all">> <<drop $storage $choiceitems 999999>> <<redo "itemlist">> <</button>> <</if>> </div> </div> <</do>> </div>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<includeplus "Storage">> </div> <<include "UI_RightBar">> </div> </div>
<<if $gamestatus.stage == 0 >> [img[setup.ImagePath +'ui/logo.png']] <</if>>
<<if $gamestatus.stage > 0 >> <<include "UI_LeftBar">> <</if>>
<<if $gamestatus.stage == 0 >><<= setup.langbank.gametitle[$lang]>><</if>>
<<set $gamestatus.stage = 0>> <<set $lang to 'en'>> <<include "LocalizationEN">> <<set $Flags = {}>> <<include "AudioData">> <<include "CodexData">> <<run PopSystem.batchAdd("human", 80, "barony")>> <<include "UI_HoverCard">> <<if setup.PatreonSystem.verifySavedHash().status === 'valid'>> <<notify 4s>>Patreon Code Activated!<</notify>> <</if>>
<<link '<i class="fa-solid fa-crown"></i>Patreon Code'>> <<SimpleUI "PatreonCode" "fit">> <</link>> <<link '<i class="fa-solid fa-screwdriver-wrench"></i>ChangeLogs'>> <<SimpleUI ChangeLogs>> <</link>> <a href="https:\\discord.gg/RNbvNpjXZW" target="_blank"><i class="fa-brands fa-discord"></i>Discord</a> <a href="https:\\www.patreon.com/AxiomNova" target="_blank"><i class="fa-brands fa-patreon"></i>Patreon</a> <a href="https:\\axiomnova.itch.io/the-last-lord" target="_blank"><i class="fa-brands fa-itch-io"></i>Itch.io</a>
<<if $gamestatus.stage == 0 >> <<= L.gamesubtitle>><br> <<include "Game_Version">> <</if>>
<<set $chars.selected = null >> <<if $chars.dataMap.has($summon.slots[0]?.id)>> <<set $summon.slots[0] = $chars.dataMap.get($summon.slots[0].id)>> <<else>> <<set $summon.slots[0] = null>> <</if>> <<if $chars.dataMap.has($summon.slots[1]?.id)>> <<set $summon.slots[1] = $chars.dataMap.get($summon.slots[1].id)>> <<else>> <<set $summon.slots[1] = null>> <</if>> <div class="summon-panel"> <div class="coupleleft"> <div class="leftTogglePanel hidden" id="leftselectpanel"> <div style="top: 0rem; position: absolute; right: -5rem; z-index: 100;"> <button id="window-close" style="right: 2rem;" onclick='$.wiki(`<<addclass ".leftTogglePanel" "hidden">><<removeclass "#leftactionpanel" "hidden">><<set $summon.currentsolt = -1 >>`)'>❌</button> </div> <div id="leftList" style="max-width: 50rem; flex: 1;"></div> </div> <div class="leftTogglePanel" id="leftactionpanel"> <<do tag "selected summon redoSummonSlot">> <<if $chars.selected && ($summon.currentsolt == 0)>> <<elseif ($chars.selected?.id == $summon.slots[0]?.id) && ($summon.currentsolt == 1)>> <<set $summon.slots[0] = null >> <<set $summon.slotsId[0] = null >> <<else>> <</if>> <div class="couplepic"> <<if $summon.slots[0]>> <div style="top: 0%; position: absolute; right: 0px; z-index: 100;"> <button id="window-close" onclick='$.wiki(`<<set $summon.slots[0] = null>><<set $summon.currentsolt = -1>><<goto "Summon_UI">>`)'>❌</button> </div> <<ShowPortrait $summon.slots[0] "left" false>> <<else>> <<if Flag('tutorial') >= 4 && visited() <= 10>> <div style="margin: auto; align-self: center;"> <<= setup.notice_summontip>> </div> <</if>> <</if>> </div> <div class="absolute-center-bottom" id="coupleaction"> <<do tag "redoSummonInfo">> <<Infocard $summon.slots[0] $summon.slots[1]>> <</do>> <<if Flag('tutorial') >= 4>> <<SummonActionFlex $summon.slots[0] 0>> <</if>> </div> <</do>> </div> </div> <div class="couplemiddle"> <div class="summonrift" id="summonrift"> [img[setup.ImagePath+'ui/bg/planarrift.webp']] </div> <div class="summon-btns"> <<include "Summon_Group">> <<do tag "summon">> <<timed 1s>> <<run setup.renderGroups()>> <</timed>> <</do>> <div class="coupledo"> <<do tag "selected summon redoSummonInfo">> <div class="coupleicon"> <<hybridBreedingToggle $summon.slots[0] $summon.slots[1]>> <div class="summonLabel"> <<link "<div id='summonset'>⚙️</div>">> <<run UI.settings();>> <</link>> <<set _teleportImg = 'ui/couple/teleport.png' >> <<if $summon.slots[0] && $summon.slots[1] && (setup.BloodlineManager.getRelationFactor($summon.slots[0], $summon.slots[1]) >= 0.5)>> <<set _teleportImg = 'ui/couple/teleport02.png' >> <<HoverTip `L.notfavored`>><span id="summonbell">🔔</span><</HoverTip>> <</if>> <<if setup.TimeSystem.isBloodMoon()>> <<set _teleportImg = 'ui/couple/teleport_red.png' >> <</if>> </div> <div class="summonbutton"> <div id="summonstart"> <<set _hybridEnergyCost = $isHybridBreeding ? 200 : 0>> <<link [img[setup.ImagePath + _teleportImg]]>> <<main>> <<if setup.TimeSystem.isBloodMoon()>> <<run Msg.flash("Blood Moon summoning is high-risk. Proceed with caution. Also, because of the magical surge, today's harvest will drains extra energy, but significantly boosts your harvest quality.")>> <<exit>> <</if>> <<if !$summon.slots[0] || !$summon.slots[1]>> <<run Msg.flash(L.summonRequirement)>> <<exit>> <</if>> <<if $summon.slots[0]?.preg || $summon.slots[1]?.preg>> <<run Msg.flash(L.duringpreg);>> <<exit>> <</if>> <<if ($manage.energy.current - (setup.harvestCost($summon.slots[0]) + setup.harvestCost($summon.slots[1])) - _hybridEnergyCost) lt 0>> <<run Msg.flash(L.energyWarning)>> <<exit>> <</if>> <<if $summon.slots[0].currentAPFloor lt 1 || $summon.slots[1].currentAPFloor lt 1>> <<run setup.Popup(setup.notice_apWarning)>> <<exit>> <</if>> <<if PopSystem.isOvercrowded("temple")>> <<run Msg.flash(L.summonmorespace)>> <<exit>> <</if>> <<run EventBus.emit("action:performed", { id: $summon.slots[0].rankId, action: "summonCount", value: 1 });>> <<run EventBus.emit("action:performed", { id: $summon.slots[1].rankId, action: "summonCount", value: 1 });>> <<if Flag('tutorial') == 5>> <<run Evm.flash('Event_Stellafall_Ritual');>> <<SetFlag 'tutorial' 6>> <<run setup.pairOnce($summon.slots[0], $summon.slots[1])>> <<exit>> <<else>> <<SummonStart $summon.slots[0] $summon.slots[1] 6>> <</if>> <</main>> <<timed 1s>><<redo "topbar redoSummonInfo">><</timed>> <</link>> </div> </div> </div> <</do>> <div id="summoninfocard"> <<do tag "selected summon redoSummonInfo">> <<if $summon.slots[0] && $summon.slots[1] && (setup.BloodlineManager.getRelationFactor($summon.slots[0], $summon.slots[1]) >= 0.5)>> <div class="flex-ssb-ac"> <span><<= "Cursed:">></span> <span>🖤<<= `${setup.BloodlineManager.getRelationDefianceText($summon.slots[0], $summon.slots[1])}`>></span> </div> <</if>> <div class="flex-ssb-ac"> <span><<= "Luck:">></span> <span>🍀<<if $summon.slots[0] && $summon.slots[1]>><<= ($summon.slots[0]?.giveFer + $summon.slots[1]?.giveFer)>><<else>>-<</if>></span> </div> <div class="flex-ssb-ac"> <span><<= "Sync:">></span> <span>🔗<<if $summon.slots[0] && $summon.slots[1] >><<= MonsterBreeder.preview($summon?.slots[0], $summon?.slots[1])>>(+<<= ($manage.summon.chanceBonus * 100).toFixed(0)>>%)<<else>>-<</if>></span> </div> <div class="flex-ssb-ac"> <span><<= "Cost:">></span> <span>⚡<<if $summon.slots[0] && $summon.slots[1] >><<= setup.harvestCost($summon.slots[0]) + setup.harvestCost($summon.slots[1]) + _hybridEnergyCost>><<else>>-<</if>></span> </div> <div class="flex-ssb-ac"> <span><<= "Current:">></span> <span>⚡<<= $manage.energy.current>></span> </div> <</do>> </div> </div> </div> </div> <div class="coupleright"> <div class="rightTogglePanel hidden" id="rightselectpanel"> <div style="top: 0rem; position: absolute; left: 0rem; z-index: 100;"> <button style="right: 0rem;" id="window-close" onclick='$.wiki(`<<addclass ".rightTogglePanel" "hidden">><<removeclass "#rightactionpanel" "hidden">><<set $summon.currentsolt = -1 >>`)'>❌</button> </div> <div id="rightList" style="max-width: 50rem; flex: 1;"></div> </div> <div class="rightTogglePanel" id="rightactionpanel"> <<do tag "selected summon redoSummonSlot">> <<if $chars.selected && ($summon.currentsolt == 1)>> <<elseif ($chars.selected?.id == $summon.slots[1]?.id) && ($summon.currentsolt == 0)>> <<set $summon.slots[1] = null >> <<set $summon.slotsId[1] = null >> <<else>> <</if>> <div class="couplepic"> <<if $summon.slots[1]>> <div style="top: 0%; position: absolute; left: 5rem; z-index: 100;"> <button id="window-close" onclick='$.wiki(`<<set $summon.slots[1] = null>><<goto "Summon_UI">>`)'>❌</button> </div> <<ShowPortrait $summon.slots[1] "right" false>> <<else>> <<if Flag('tutorial') >= 4 && visited() <= 10>> <div style="margin: auto; align-self: center;"> <<= setup.notice_summontip>> </div> <</if>> <</if>> </div> <div class="absolute-center-bottom" id="coupleaction"> <<do tag "redoSummonInfo">> <<Infocard $summon.slots[1] $summon.slots[0]>> <</do>> <<if Flag('tutorial') >= 4>> <<SummonActionFlex $summon.slots[1] 1>> <</if>> </div> <</do>> </div> </div> </div> <<set _FacHelp = "CodexEntry_Building_PlanarRift" >> <<done>> <<timed 0.2s>> <<if Flag('tutorial') == 2>> <<append "#pageClose">> <<set _tutorl = [ { el: '#pageClose', text: 'Click "Back" to return to the MainHall.' } ] >> <<TutorList _tutorl>> <</append>> <</if>> <<if Flag('tutorial') == 5>> <<append "#summonstart">> <<set _tutorl = [ { el: '#summonstart', text: L.summonStartButton } ] >> <<TutorList _tutorl>> <</append>> <</if>> <</timed>> <</done>>
<<widget "SummonActionFlex">> <<set _actionMon = _args[0] >> <<set _actionSide = _args[1] >> <<capture _actionMon _actionSide>> <div class="action-row-btns"> <div class="flex-col-center"> <div id="actionicon"> <<link "<<showframe 'icon64' 'Quick' 4.5>>">> <<set $summon.currentsolt = _actionSide>> <<if _actionSide == 0>> <<replace "#rightList">><<MapList $chars.templeList>><</replace>> <<replace "#leftList">><</replace>> <<addclass '.rightTogglePanel' 'hidden'>> <<removeclass "#rightselectpanel" 'hidden'>> <<elseif _actionSide == 1>> <<replace "#leftList">><<MapList $chars.templeList>><</replace>> <<replace "#rightList">><</replace>> <<addclass '.leftTogglePanel' 'hidden'>> <<removeclass "#leftselectpanel" 'hidden'>> <<else>> <</if>> <</link>> </div> <span id="actiontext"><<= "Quick">></span> </div> <div class="flex-col-center"> <div id="actionicon"> <<link "<<showframe 'icon64' 'Detail' 4.5>>">> <<set $summon.currentsolt = _actionSide>> <<goto "Monsters_UI">> <</link>> </div> <span id="actiontext"><<= "Detail">></span> </div> <<if Flag('tutorial') >= 8>> <div class="flex-col-center"> <div id="actionicon"> <<link "<<showframe 'icon64' 'Feed' 4.5>>">> <<if _actionMon>> <<SetFlag 'enblefeed'>> <<set $manage.feed.feedCp.charID = _actionMon.id >> <<SimpleUI "Storage">> <<else>> <<run Msg.flash(L.selecttarget)>> <</if>> <</link>> </div> <span id="actiontext"><<= L.feed>></span> </div> <div class="flex-col-center"> <div id="actionicon"> <<link "<<showframe 'icon64' 'Harvest' 4.5>>">> <<if _actionMon>> <<HarvestMon _actionMon>><<redo "topbar">><<redo "summon">> <<else>> <<run Msg.flash(L.selecttarget)>> <</if>> <</link>> </div> <span id="actiontext"><<= L.harvest>></span> </div> <</if>> </div> <</capture>> <</widget>>
<div class="summon-group"> <div class="section"> <div class="group-list" id="group-list"></div> <div class="controls"> <button id="save-btn" class="btn btn-secondary"><i class="fa-solid fa-star"></i><i class="fa-solid fa-user-group"></i></button> <button id="clear-btn" class="btn btn-primary"><i class="fa-solid fa-eye"></i><i class="fa-solid fa-user-group"></i></button> </div> </div> </div> <div id="modal-overlay" style="display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.8); z-index:9999; align-items:center; justify-content:center;"> <div style="background:#1a1a2e; padding:20px; border:1px solid #457b9d; color:white;"> <h3>Group name</h3> <input type="text" id="modal-input" class="name-input"> <button id="modal-confirm" class="btn btn-primary">Confirm</button> <button id="modal-cancel" class="btn btn-secondary">Cancel</button> </div> </div> <<done>><<script>> const charData = State.variables.chars; setup.renderGroups = renderGroups; function init() { renderGroups(); setupEventListeners(); } function renderGroups() { const groupList = document.getElementById('group-list'); groupList.innerHTML = ''; if (charData.groups.length === 0) { groupList.innerHTML = '<div class="empty-message">No Groups</div>'; return; } charData.groups.forEach((group, index) => { const item = document.createElement('div'); item.className = 'group-item'; item.dataset.index = index; group.characters = group.characters.filter(char => charData.dataMap.get(char) !== undefined); item.innerHTML = ` <div class="group-header"> <input type="text" class="name-input group-name-input" value="${group.name}" style="min-width: 7rem;"> <div class="group-actions"> <button class="btn btn-small btn-secondary load-btn"><i class="fa-solid fa-arrow-up-from-bracket"></i></button> <button class="gpaction-btn delete-btn">×</button> </div> </div> <div style="font-size: 0.8rem;"> ${group.characters.map((char, posIndex) => ` <div class="character-info"> <span>${setup.gender2pic[charData.dataMap.get(char).gender]}</span> <span>${charData.dataMap.get(char).name}</span> <span><i class="fa-solid fa-heart"></i>${Math.trunc(charData.dataMap.get(char).currentAPFloor)}</span> <span>Lv.${charData.dataMap.get(char).level.lv}</span> <span><i class="fa-solid fa-seedling"></i>${charData.dataMap.get(char).giveFer}%</span> </div> `).join('')} </div> `; const loadBtn = item.querySelector('.load-btn'); loadBtn.addEventListener('click', () => { loadGroup(index); }); const deleteBtn = item.querySelector('.delete-btn'); deleteBtn.addEventListener('click', () => { deleteGroup(index); }); const nameInput = item.querySelector('.group-name-input'); nameInput.addEventListener('change', () => { renameGroup(index, nameInput.value); }); groupList.appendChild(item); }); } function saveToGroup() { const hasCharacters = State.variables.summon.slots.some(slot => slot !== null); if (!hasCharacters) return; setup.Popup(`<<textbox "_groupname" "Group ${charData.groups.length + 1}">>`, { title: "Group Name", buttons: [ 'cancel', { text: "Confirm", class: "btn-red", confirm: () => { if (!State.temporary.groupname) return; const characters = State.variables.summon.slots .filter(slot => slot !== null) .map(char => char.id); charData.groups.push({ name: State.temporary.groupname, characters: characters }); renderGroups(); } }] }); } function loadGroup(index) { const group = charData.groups[index]; group.characters = group.characters.filter(id => charData.dataMap.has(id)); const slots = State.variables.summon.slots; const chars = group.characters.map(id => charData.dataMap.get(id)); const emptyCount = slots.filter(s => s == null || s === '').length; if (chars.length === 2) { if (emptyCount >= 2) { setup.setSlotChar(slots, null, chars[0]); setup.setSlotChar(slots, null, chars[1]); } else { setup.setSlotChar(slots, 0, chars[0]); setup.setSlotChar(slots, 1, chars[1]); } } else if (chars.length === 1) { const hasEmpty = emptyCount > 0; setup.setSlotChar(slots, hasEmpty ? null : 0, chars[0]); } $.wiki(`<<redo "selected">>`); } function deleteGroup(index) { charData.groups.splice(index, 1); renderGroups(); } function renameGroup(index, newName) { if (newName.trim()) { charData.groups[index].name = newName.trim(); } } function showGroups() { const groupList = document.getElementById('group-list'); groupList.style.display = groupList.style.display == 'flex' ? 'none' : 'flex'; } function setupEventListeners() { document.getElementById('save-btn').addEventListener('click', saveToGroup); document.getElementById('clear-btn').addEventListener('click', showGroups); } init(); <</script>><</done>>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<includeplus "Summon">> </div> <<include "UI_RightBar">> </div> </div>
<<widget SummonStart>> <<set _imglink = coupledata(_args[0], _args[1])>> <<if _imglink == null>> <<DualSummon $summon.slots[0] $summon.slots[1]>> <<else>> <<SimpleWiki "<<ShowSummonCG _imglink>>" "fullpop">> <</if>> <</widget>> <<widget DualSummon>> <<nobr>> <<CardPop _args[0] _args[1] $manage.cardSelect>> <<run setup.pairOnce(_args[0], _args[1])>> <</nobr>> <</widget>> <<widget MergeWrong>> <<set _newmonster = mixMonsters(_args[0], _args[1], false)>> <<set _newmonster.gender = "girl" >> <<set _newmonster.name = setup.getname("girl") >> <<set _newmonster.id = setup.newID() + "wrong">> <<set _newmonster.rank = 0 >> <<set _newmonster.type = "wrong" >> <<set _newmonster.height = 68 >> <<set _newmonster.img = `bestiary/${_newmonster.race}/rank0${_newmonster.race}.webp`>> <<for _attrn range setup.coreStats>> <<set _newmonster[_attrn].op = random(0, 1)>> <</for>> <<set _newmonster.look.base = random(0, 30) >> <<run _newmonster.parental = [{ id: _args[0].id, name: _args[0].name, gender: _args[0].gender, race: _args[0].race } ,{ id: _args[1].id, name: _args[1].name, gender: _args[1].gender, race: _args[1].race }]>> <<run _newmonster.traits = {}>> <<run _newmonster.addTrait("cursed", 1)>> <<set $addmonster = _newmonster>> <<set $manage.seedCount += 1 >> <</widget>> <<widget MergeMonsters>> <<set _newmonster = mixMonsters(_args[0], _args[1], _args[2])>> <<if _newmonster.gender != "boy">><<run _newmonster.addTrait("virginalpurity", 1);>><</if>> <<set $addmonster = _newmonster>> <<set $manage.seedCount += 1 >> <</widget>> <<widget "hybridBreedingToggle">> <<set _check = setup.hybridManager.canHybridize(_args[0], _args[1])>> <<if $isHybridBreeding === undefined>> <<set $isHybridBreeding = false>> <</if>> <<if !_check.allowed>> <<set $isHybridBreeding = false>> <</if>> <span id="hybrid-toggle-container"> <<set _tooltipText = "">> <<if !_check.allowed>> <<set _tooltipText = "🔒 Locked: Requires " + (_check.lockedRaces ? _check.lockedRaces.join(" & ") : "Species") + " Hybrid Fusion Tech">> <<elseif $isHybridBreeding>> <<set _tooltipText = "⚡ Active: Hybrid Fusion (WIP) (Costs Extra 200 Energy).">> <<else>> <<set _tooltipText = "🧬 Enable Hybrid Fusion (WIP) (Costs Extra 200 Energy).">> <</if>> <div class="hybrid-toggle-container"> <div class="hybrid-tooltip-wrapper" @data-tooltip="_tooltipText"> <<if _check.allowed>> <label @class="'hybrid-label ' + ($isHybridBreeding ? 'checked' : '')"> <span class="hybrid-checkbox-custom"></span> <input type="checkbox" @checked="$isHybridBreeding" onchange="sv.isHybridBreeding = !sv.isHybridBreeding; document.dispatchEvent(new CustomEvent(':redo', { detail: { tags: ['redoSummonInfo'] } }));"> <span class="hybrid-icon">🧬</span> </label> <<else>> <label class="hybrid-label disabled"> <span class="hybrid-checkbox-custom"></span> <input type="checkbox" disabled> <span class="hybrid-icon">🧬</span> </label> <</if>> </div> </div> </span> <</widget>>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<includeplus "TechTree">> </div> <<include "UI_RightBar">> </div> </div>
<<widget "TraitCard">> <<set _key = _args[0]>> <<set _info to setup.queryTrait(_args[0])>> <<if _info>> <div class="page-card card-slot"> <div class="slab-header"> <div class="slab-icon"><<= setup.getTraitIconByID(_key)>></div> <div class="slab-title-group"> <span class="trait-name"><<= setup.getTraitNameByID(_key)>></span> </div> <<if _info.madeFrom && _info.madeFrom.length > 0>> <span class="trait-tier tier-high">TIER <<= _info.madeFrom[0].tier>></span> <<elseif _info.tier === 2>> <span class="trait-tier tier-rare"><<= "RARE">></span> <<else>> <span class="trait-tier tier-base">BASE</span> <</if>> </div> <div class="slab-desc"><<= setup.getTraitDescByID(_key)>></div> <div class="alchemy-card"> <div class="card-body"> <div class="info-group"> <div class="group-title">🔍Formula:</div> <<if _info.madeFrom && _info.madeFrom.length > 0>> <<for _recipe range _info.madeFrom>> <div class="fusion-item"> = <<= _recipe.src.join(" + ")>> </div> <</for>> <<else>> <span class="fusion-item">Unknown</span> <</if>> </div> <div class="info-group"> <div class="group-title">🧪Fusion:</div> <<if _info.fusesInto.length > 0>> <<for _recipe range _info.fusesInto>> <div class="fusion-item"> <span class="plus">+</span> <span class="needs"><<= _recipe.needs.join(", ")>></span> <span class="arrow">➜</span> <span class="result"><<= _recipe.result>></span> <span class="tier-tag">T<<= _recipe.tier>></span> </div> <</for>> <<else>> <span class="fusion-item">Unknown</span> <</if>> </div> <div class="info-group conflict-group"> <div class="group-title">⚔️Conflicts:</div> <div class="conflict-list"> <<if _info.conflicts.length > 0>> <<for _enemy range _info.conflicts>> <span class="conflict-tag">- <<= _enemy>></span> <</for>> <<else>> <span class="fusion-item">Unknown</span> <</if>> </div> </div> </div> </div> </div> <</if>> <</widget>>
<div class="civ-ui-wrapper"> <div class="compact-civ-ui" id="civ-ui-trigger"> <div id="civ-top-bar" style="display:flex; width:100%; justify-content:center; gap:0.5rem; flex-wrap:wrap;"></div> </div> <div class="civ-modal-overlay" id="civ-modal-overlay"> <div class="civ-modal"> <div class="civ-modal-header"> <div class="civ-modal-title"> Barony Analytics </div> <div id="civ-header-alert"></div> <div id="civ-header-stats" class="civ-header-stats"></div> <button class="civ-close-btn" id="civ-close-btn">×</button> </div> <div class="civ-modal-body"> <div class="panel-section"> <div class="panel-title"><i class="fas fa-chart-pie"></i> Civilization</div> <div class="pie-wrapper"> <div class="pie-chart" id="civ-pie-chart"></div> <div class="pie-center" id="pie-center-val"></div> </div> <div class="pie-legend-list" id="pie-legend-list"></div> <div id="civ-breakdown-list" style="display:none; flex-direction:column; gap:0.4rem;"></div> <div style="margin-top: 1.5rem; padding-top: 1rem; border-top: 1px dashed #4a5568;"> <div class="panel-title"><i class="fas fa-venus-mars"></i> Gender Ratio</div> <div style="display: flex; align-items: center; gap: 1rem;"> <div class="pie-wrapper" style="width: 6rem; height: 6rem; margin: 0; flex-shrink: 0;"> <div class="pie-chart" id="gender-pie-chart"></div> <div class="pie-center" style="background: #1a202c;"> <i class="fas fa-users" style="color: #718096; font-size: 0.9rem;"></i> </div> </div> <div class="pie-legend-list" id="gender-legend-list" style="flex: 1;"></div> </div> </div> </div> <div class="panel-section"> <div class="panel-title"><i class="fas fa-chart-bar"></i> Metrics</div> <div class="chart-box"> <div style="font-size:0.8rem; opacity:0.7; margin-bottom:0.5rem; text-align:center">Civilization Level</div> <div class="bar-chart-row" id="civ-chart-level"></div> </div> <div class="chart-box"> <div style="font-size:0.8rem; opacity:0.7; margin-bottom:0.5rem; text-align:center">Combat Power</div> <div class="bar-chart-row" id="civ-chart-power"></div> </div> </div> <div class="panel-section"> <div class="panel-title"><i class="fas fa-home"></i> Capacity</div> <div id="civ-housing-panel" class="housing-box"> </div> <div class="panel-title"><i class="fas fa-th-large"></i> Factions</div> <div class="civ-details-grid" id="civ-detail-grid"></div> </div> </div> </div> </div> </div> <<done>> <<run CivSystem.init()>> <<run CivSystem.renderTopBar()>> <</done>>
<<set setup.geneModBrief = `<div style="background-color: #1a1a1a; padding: 12px; border: 1px solid #444;"> <strong style="display: block; margin-bottom: 5px;">Genetic Evolution</strong> <hr style="border: 0; border-top: 1px solid #444; margin: 8px 0;"> <p style="font-size: 0.9em; margin: 5px 0;"> <span style="color: #7289da;">Innate Mod</span>: Raw genetic base inherited at birth. </p> <div style="background-color: #262626; padding: 8px; border-radius: 4px; text-align: center; margin: 10px 0; font-family: monospace; color: #ffd700;"> Innate Mod × Training = Inheritable Mod </div> <p style="font-size: 0.9em; margin: 5px 0;"> <span style="color: #ff99ff;">Training</span> refines base stats into <span style="color: #7289da;">Inheritable Mod</span>—the potential passed to future generations. </p> <hr style="border: 0; border-top: 1px solid #444; margin: 8px 0;"> <p style="font-size: 0.85em; margin: 5px 0; font-style: italic; color: var(--text-secondary);"> Breeding: Inheritable Mod - Loss + Mutation = Offspring Innate </p> </div>`>> <<set setup.freeModeNotice = `<div> <strong style="display: block; margin-bottom: 10px; font-size: 1.1em;">Free Mode Activated</strong> <ul style="margin: 0; padding-left: 20px; line-height: 1.6;"> <li>Bypass tutorials & select story quests.</li> <li>Access all building tiers immediately.</li> <li>Timeline set to Week 4 (Post-Traversal).</li> <li><strong>Note:</strong> Independent of main story progress.</li> </ul> <p style="margin: 15px 0 0 0; font-size: 0.9em; color: #ffcc00;"> First time playing? Use <em>Standard Mode</em> for the intended experience. </p> <p style="margin: 10px 0 0 0; font-size: 0.8em; color: #888; border-top: 1px solid #333; padding-top: 8px;"> <em>Beta Feature: Please report any bugs found in this mode.</em> </p> </div>` >> <<set setup.notice_eninfused = "A device infused with planar energy, used to unlock and boost Normal Pregnancy chances, with upgrades increasing effectiveness.<br>Please click the [Girls] in the Bedroom." >> <<set setup.notice_infusecost = "-500⚡Energy, -1❤️AP(target), 100% chance of 🤰pregnancy, or accelerated pregnancy by <<= $manage.preg.infuserDays>> days.<br>You can increase infusion days on the Building Upgrade interface." >> <<set setup.notice_moveout = `<div style="line-height: 1.5;"> <p style="margin: 0 0 10px 0;"><strong>Relocate to Barony</strong></p> <p style="margin: 0 0 10px 0;"> Assign this unit to live as a commoner within the Barony. They will leave the temple to contribute to the local population and labor force. </p> <p style="margin: 0 0 10px 0; color: #ffcc00;"> <strong>Note:</strong> This transition is permanent. Once they move out, they cannot return to the temple. </p> </div> `>> <<set setup.notice_clearsoul = `<div style="background: #1a0b1f;padding: 16px;"> <h2 style="color: #e74c3c;margin: 1rem 0;">⚠️ Clear Soul Data</h2> <p> This action will permanently purge all your progress: <b>Achievements, Bestiary, Training Actions, and your accumulated Soul Essence</b>.<br><br> Unlike deleting a standard save file, this is a <b>total reset of your legacy</b>. You will be starting from a blank slate. Are you absolutely certain? </p></div>` >> <<set setup.notice_apWarning = `<div style="color: #ffcc00; font-weight: bold; margin-bottom: 8px; font-size: 1.1em;"> ⚠️ AP Depleted </div> <div style="margin-bottom: 10px;">At least one selected character has <strong>zero AP❤️</strong>. You can:</div> <div style="margin-left: 10px;"> <div style="margin: 4px 0;">1. <span style="font-weight: bold;">Rest</span> in the bedroom to recover AP.</div> <div style="margin: 4px 0;">2. <span style="font-weight: bold;">Switch</span> to a character with remaining AP.</div> <div style="margin: 4px 0;">3. <span style="font-weight: bold;">Feed</span> items to boost AP.</div> </div>` >> <<set setup.notice_summontip = `<div style="max-width: 28rem;font-size: 0.9em;"> <div style="margin-bottom: 8px;"> • Click <strong style="color: #f1c40f;">"Quick"</strong> to open the quick selection menu. <i style="display: block; margin-top: 4px; color: #aaa; font-size: 0.85em;">Tip: Double-click any portrait to select it immediately.</i> </div> <div> • Click <strong style="color: #f1c40f;">"Detail"</strong> to open the Warren page for comprehensive stats; use the <strong>"Summon"</strong> button at the bottom to return here. </div> </div>` >>
<div class="meeting-sidebar"> <div style="text-align: center;color: var(--sp-canine); font-weight: bold; line-height: 1.2; padding: 1rem; font-size: 1.5rem;">Hawkridge Barony</div> <div class="sidebar-group sidebar-domain"> <h2>Hawkridge - Pass</h2> <div class="sidebar-group-buttons"> <<if Flag('enbastion') == 2>><div class="sidebar-button" data-passage="Battle_UI"><span class="sidebar-icon">🛡️</span><<= "Bastion">></div><</if>> <<if $times.weekday == 4 || $times.total >= 25>><div class="sidebar-button" data-passage="Market_UI"><span class="sidebar-icon">💰</span><<= "Market">></div><</if>> <<if $times.weekday == 2>><div class="sidebar-button" data-passage="Slaver_UI"><span class="sidebar-icon">⛓️</span><<= "Slaver">></div><</if>> </div> </div> <<if Flag('tutorial') >= 6>> <div class="sidebar-group sidebar-domain"> <h2>Management</h2> <div class="sidebar-group-buttons"> <div class="sidebar-button" onclick='$.wiki(`<<SimpleUI "Quest">>`)'><span class="sidebar-icon">⁉️</span>Quests</div> <<if Flag('tutorial') >= 10>><div class="sidebar-button" onclick='$.wiki(`<<SimpleUI "DomainReport">>`)'><span class="sidebar-icon">📊</span>Report</div><</if>> <<if Flag('tutorial') >= 10>><div class="sidebar-button" onclick='$.wiki(`<<SimpleUI "Calendar" "fit">>`)'><span class="sidebar-icon">📆</span>Calendar</div><</if>> <<if Flag('tutorial') >= 10>><div class="sidebar-button" onclick='$.wiki(`<<SimpleUI "PopInfo" "fit">>`)'><span class="sidebar-icon">🗃️</span>Pop.</div><</if>> </div> </div> <</if>> <div class="sidebar-group sidebar-domain"> <h2>System</h2> <div class="sidebar-group-buttons"> <div class="sidebar-button" onclick='$.wiki(`<<run UI.settings();>>`)'><span class="sidebar-icon">⚙️</span>Settings</div> <div class="sidebar-button" onclick='$.wiki(`<<SimpleUI "Meta_Achievement" "full">>`)'><span class="sidebar-icon">🏆</span>Archive</div> <div class="sidebar-button" onclick='$.wiki(`<<ToHome>>`)'><span class="sidebar-icon">🏰</span>Home</div> </div> </div> </div>
<<EventRadarShow>> <div class="RightBar"> <div class="rightbar-btn" id="pageClose"> <<link "<<showframe 'icon64' 'close' 3.5>><span id='maptext'><<= 'Back'>></span>">><<if Flag('tutorial') == 2>><<SetFlag 'tutorial' 3>><</if>> <<replace "#passagecontent">><<includeplus "MainHall">><</replace>> <</link>> </div> <div class="funcbar"> <<if Flag('tutorial') >= 6>> <div class="rightbar-btn" id="mapbtn"> <<link "<<showframe 'icon64' 'maps' 3.5>><span id='maptext'><<= 'Maps'>></span>">> <<replace "#passagecontent">><<includeplus "MapsPopup">><</replace>> <</link>> </div> <</if>> <<if Flag('enmeeting') == 2>> <div class="rightbar-btn"> <<link "<<showframe 'icon64' 'meeting' 3.5>><span id='maptext'><<= 'Meeting'>></span>">> <<replace "#passagecontent">><<includeplus "Meeting">><</replace>> <</link>> </div> <</if>> <<if Flag('enmeeting') == 2>> <div class="rightbar-btn"> <<link "<<widthframe 'resource56' 'workforce' 3.5>><span id='maptext'><<= 'Assign'>></span>">> <<replace "#passagecontent">><<includeplus "Job">><</replace>> <</link>> </div> <</if>> <<if Flag('tutorial') >= 4>> <div class="rightbar-btn"> <<link "<<showframe 'species64' 'feline' 3.5>><span id='maptext'><<= 'Warren'>></span>">> <<replace "#passagecontent">><<includeplus "Monsters">><</replace>> <</link>> </div> <</if>> <<if Flag('tutorial') >= 6>> <div class="rightbar-btn"> <<link "<<showframe 'icon64' 'rift' 3.5>><span id='maptext'><<= 'Summon'>></span>">> <<replace "#passagecontent">><<includeplus "Summon">><</replace>> <</link>> </div> <</if>> <<if Flag('enbuild') == 2>> <div class="rightbar-btn"> <<link "<<showframe 'icon64' 'upgrade' 3.5>><span id='maptext'><<= 'Building'>></span>">> <<replace "#passagecontent">><<includeplus "Building">><</replace>> <</link>> </div> <</if>> <<if Flag('endispatch') == 2>> <div class="rightbar-btn"> <<link "<<showframe 'icon64' 'dispatch' 3.5>><span id='maptext'><<= 'Dispatch'>></span>">> <<replace "#passagecontent">><<includeplus "Dispatch">><</replace>> <</link>> </div> <</if>> </div> <div class="eventbar"> <<if $times.weekday == 4>> <div class="rightbar-btn glow-market"> <<link "<<showframe 'icon64' 'blackmarket' 3.5>><span id='maptext'><<= 'Market'>></span>">> <<replace "#passagecontent">><<includeplus "Market">><</replace>> <</link>> </div> <</if>> <<if $times.weekday == 2>> <div class="rightbar-btn glow-slaver"> <<link "<<showframe 'icon64' 'slaver' 3.5>><span id='maptext'><<= 'Slaver'>></span>">> <<replace "#passagecontent">><<includeplus "Slaver">><</replace>> <</link>> </div> <</if>> <<if Flag('enbastion') == 2 && Flag('battleday')>> <div class="rightbar-btn glow-battle"> <<link "<<showframe 'icon64' 'battle' 3.5>><span id='maptext'><<= 'Battle'>></span>">> <<goto "Battle_UI">> <</link>> </div> <</if>> </div> <<timed 0.1s>> <div class="bdoperate"> <<if Flag('enbedroom') == 2>> <div class="rightbar-btn"> <<link "<<showframe 'icon64' 'bedroom' 3.5>><span id='maptext'><<= 'Bedroom'>></span>">> <<goto "Bedroom_UI">> <</link>> </div> <</if>> <<if Flag('enstorage') == 2>> <div class="rightbar-btn"> <<link "<<showframe 'icon64' 'storage' 3.5>><span id='maptext'><<= 'Storage'>></span>">> <<replace "#passagecontent">><<includeplus "Storage">><</replace>> <</link>> </div> <</if>> <<if Flag('enbedroom') == 2>> <div class="rightbar-btn"> <<link "<<showframe 'icon64' 'bestiary' 3.5>><span id='maptext'><<= 'Bestiary'>></span>">> <<replace "#passagecontent">><<includeplus "Bestiary">><</replace>> <</link>> </div> <</if>> <<if Flag('enstudy') == 2>> <div class="rightbar-btn"> <<link "<<showframe 'icon64' 'evolution' 3.5>><span id='maptext'><<= 'Study'>></span>">> <<replace "#passagecontent">><<includeplus "TechTree">><</replace>> <</link>> </div> <</if>> <<if Flag('enbedroom') == 2>> <div class="rightbar-btn"> <<link "<<showframe 'icon64' 'help' 3.5>><span id='maptext'><<= 'Codex'>></span>">> <<if def _FacHelp>> <<goto "Codex_UI">><<include _FacHelp>><<timed 0.1s>> <<replace '#codexContent'>> <<= setup.entrycontent[$lang]>> <</replace>><</timed>> <<else>> <<replace "#passagecontent">><<includeplus "Codex">><</replace>> <</if>> <</link>> </div> <</if>> </div> <</timed>> </div>
<<if tags().includes("topbar")>> <div class="UItopbar"><<do tag "topbar">> <<set _jobCache = JobSystem.getPreview()>> <div id="topbar-box"> <div class="topbar-grid" style="min-width: 40rem;"> <<HoverTip "<<= 'Food'>><br><<= 'Yield: +'>><<= _jobCache.totalIncome.food || 0>> /day<br><<= 'Consume: -' + PopSystem.getFoodConsumption()>> /day">><<showframe 'resource56' 'food' 1.75>><<= $storage.count('food')>><</HoverTip>> <<HoverTip "<<= 'Gold'>><br><<= 'Yield: +'>><<= _jobCache.totalIncome.gold || 0>> /day">><<showframe 'resource56' 'gold' 1.75>><<= $storage.count('gold')>><</HoverTip>> <<HoverTip "<<= 'Wood'>><br><<= 'Yield: +'>><<= _jobCache.totalIncome.wood || 0>> /day">><<showframe 'resource56' 'wood' 1.75>><<= $storage.count('wood')>><</HoverTip>> <<HoverTip "<<= 'Stone'>><br><<= 'Yield: +'>><<= _jobCache.totalIncome.stone || 0>> /day">><<showframe 'resource56' 'stone' 1.75>><<= $storage.count('stone')>><</HoverTip>> <<HoverTip "<<= 'Ore'>><br><<= 'Yield: +'>><<= _jobCache.totalIncome.ore || 0>> /day">><<showframe 'resource56' 'ore' 1.75>><<= $storage.count('ore')>><</HoverTip>> <<HoverTip "<<= 'Crystal'>><br><<= 'Yield: +'>><<= _jobCache.totalIncome.crystal || 0>> /day">><<showframe 'resource56' 'crystal' 1.75>><<= $storage.count('crystal')>><</HoverTip>> <<HoverTip "Workforce<br>Total: <<= $domain.getWorkforce()>><br>Expended: <<= $domain.getUnitsWF()>>">><<showframe 'resource56' 'workforce' 1.75>><<= $domain.getWorkforce()>><</HoverTip>> <<HoverTip "Piety<br>Workforce: <<=$domain.getPiety()>>%<br> Soldiers: +<<=$domain.getPiety()>>%<br>Provides conversion rates for <br>population and various stats.">><<showframe 'resource56' 'piety' 1.75>><<= $domain.getPiety()>><</HoverTip>> <<HoverTip 'Energy<br>Current: $manage.energy.current <br>Max: <<= $manage.getMaxEnergy()>> <br>Regen: +$manage.energy.regen /day'>>⚡<span style="font-size:1.3rem;color:orange;"><<= $manage.energy.current >></span><</HoverTip>> </div> <div class="topbar-grid" style="min-width: 26rem;"> <<if Flag('enbedroom') == 2>> <span style="display: flex;"><<showframe 'resource56' 'population' 1.75>><<include "UI_CivBar">></span> <</if>> </div> <div class="topbar-grid" style="min-width: 26rem;"> <<link "<span>📆 <<= setup.TimeSystem.getCurrentInfo()>></span>">><<run setup.Popup(`<<include "Calendar">>`)>><</link>> </div> <div class="topbar-grid" style="margin-left: auto;"> <<if SimpleAudio.mute()>><<link "<span class='topbar-setting'>🔇</span>">><<run SimpleAudio.mute(false);>><<redo "topbar">><</link>><<else>><<link "<span class='topbar-setting'>🔊</span>">><<run SimpleAudio.mute(true);>><<redo "topbar">><</link>><</if>> <<link "<span class='topbar-setting'>🖥️</span>">><<run setup.Fullscreen.toggle()>><</link>> <<link "<span class='topbar-setting'>⚙️</span>">><<run UI.settings();>><</link>> </div> </div> <</do>> </div> <</if>>
<<run AudioBus.playPlaylist('index_order');>> <<script>> $(document).ready(function(){ const backgrounds = ['images/ui/bg/dungeon01.webp', 'images/ui/bg/temple01.webp', "images/ui/bg/lost.webp"]; $('.start-page').css('background-image', `url(${backgrounds.random()})`); }); <</script>> <<run UIBar.unstow().show();>> <div class="start-page backimage-center cinematic-menu"> <div class="menu-vignette"></div> <div id="monstershow"> <<set _tier = random(1, 3) >> <<set _titleMon = createMonster("sirenia", _tier) >> <<timed 0.2s>><<ShowPortrait _titleMon "titleMon" false>><</timed>> </div> <div class="start-content"> <<do tag "lang">> <div class="header-container"> <h1 class="game-title"><<= L.gametitle>></h1> <div class="mod-banner"> <span class="mod-name"><<= L.gamesubtitle>></span> <div class="mod-tags"> <span class="mod-tag mod">MOD</span> </div> </div> </div> <</do>> <<do tag "indexMenu">> <div class="buttons-group-row welcome-btngroup" style="grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));"> <<link 'New Start'>><<run UIBar.stow()>><<goto "Welcome_NewGame">><</link>> <<link 'Load Game'>><<run UI.saves();>><</link>> <<link 'Free Mode'>><<run UIBar.stow()>><<goto 'Welcome_FreeMode'>><</link>> <<link 'Achievements'>><<SimpleUI "Meta_Achievement" "full">><</link>> <<link 'Exit Game'>><<run window.close();>><</link>> </div> <</do>> <div class="settings-module" id="settings-module"> <div class="settings-content"> <div class="settings-option" title="language"> <span>🌐</span> <<set _langOptions = { "English" : "en"} >> <<listbox "$lang" autoselect>> <<optionsfrom _langOptions>> <</listbox>> </div> <div class="settings-option" title="fullscreen"> <<link "🖥️ Fullscreen">><<run setup.Fullscreen.toggle();>><</link>> </div> <div class="settings-option"> <<link "⚙️ Settings">><<run UI.settings();>><</link>> </div> <div class="settings-option" title="Clear Soul Data"> <<link "⚠️ Clear Soul">> <<run setup.Popup(`<<= setup.notice_clearsoul>>`,{class:"popup-notice",buttons: ['cancel', {text: "Clear",confirm: () => { State.metadata.clear();Engine.restart();}}]});>> <</link>> </div> </div> </div> </div> </div>
<link href="https:\\fonts.googleapis.com/css2?family=Cinzel:wght@400;700;900&family=Marcellus&display=swap" rel="stylesheet"> <<set _storyact = $gamestatus.stage >> <<set _storyacts = [ {"stage":"Act 0", "title":"", "desc":"", "passage":""}, {"stage":"Act 1", "title":"Hello World", "desc":"", "passage":"Welcome_Intro3"}, {"stage":"Act 2", "title":"Firewall", "desc":"", "passage":"MainHall_UI"}, {"stage":"Act 3", "title":"Update", "desc":"", "passage":"MainHall_UI"}, {"stage":"Act 4", "title":"Handshake", "desc":"", "passage":"MainHall_UI"}, {"stage":"Act 5", "title":"Backdoor", "desc":"", "passage":"MainHall_UI"}, ] >> <div class="storyact-container"> <div class="game-title curtain-title act-number"><<= _storyacts[_storyact].stage>></div> <div class="decoration decoration-left"></div> <div class="decoration decoration-right"></div> <div class="act-content" id="actContent"> <div class="act-title" id="actTitle"><<= _storyacts[_storyact].title>></div> <div class="act-description" id="actDescription"><<= _storyacts[_storyact].desc>></div> </div> </div> <<timed 4.5s>><<goto _storyacts[_storyact].passage>><</timed>>
<<if Meta.totalGems > 0>> <<run setup.Popup(`<<AstralArchive>>`, { mandatory: true, buttons: [ { text: "Return", cancel: () => $.wiki(`<<goto "Welcome">>`) }, { text: "Awaken", confirm: () => $.wiki(`<<InheritData>><<silent>><<include "Game_Init">><<SkipTutorial>><</silent>><<goto "MainHall_UI">>`) }] });>> <<else>> <<silent>><<include "Game_Init">><<SkipTutorial>><</silent>><<goto "UI_PassageHub">> <</if>>
<<run UIBar.hide();>> <style> body { background-color: var(--bg-body); } .container { position: relative; width: 100%; height: 100%; overflow: hidden; } .layer { position: absolute; top: 0; left: 0; height: 100%; opacity: 0; transition: opacity 1s ease-in-out; background-size: cover; background-position: center; } .layer img{ height:100%; } .div01 { left: 0; } .div02 { left: 20%; } .div03 { left: 40%; } .div04 { left: 60%; } .div01 { animation: fadeIn 1s forwards 1.5s; } .div02 { animation: fadeIn 1s forwards 3s; } .div03 { animation: fadeIn 1s forwards 4.5s; } .div04 { animation: fadeIn 1s forwards 6s; } </style> <div class="container"> <div class="layer div01">[img[setup.ImagePath+'event/intro/intro01.webp']]</div> <div class="layer div02"> [img[setup.ImagePath+'event/intro/intro02.webp']]</div> <div class="layer div03"> [img[setup.ImagePath+'event/intro/intro03.webp']]</div> <div class="layer div04">[img[setup.ImagePath+'event/intro/intro04.webp']]</div> </div> <<timed 8s>><<goto "Welcome_Intro2">><</timed>> <<run AudioBus.playPlaylist('start_game');>>
<div style="display: flex;flex-direction: column;justify-content: center;align-items: center;height: 85%;text-align: center;"> <div style="margin-top: 60px;"> <p><h4 style="font-style: italic;letter-spacing: 0px;">(The voice ripples through your mind—cold, ancient, and absolute.)</h4></p> <p><h2 style="font-size: 28px;font-style: italic;letter-spacing: 3px;font-family: 'Georgia', serif;">What is the value of your soul?</h2></p> </div> <div style="margin-top: 40px;"> <<button '<div style="padding: 6px 30px;font-size: 1rem;">Continue</div>' >> <<script>> Dialog.create("The Echo", "Identification"); Dialog.append( "<p>Before you are anchored in this new plane of existence, <br>surrender your name:</p>" + "<input type='text' id='name-input' value='Wilderen' style='width: 80%; padding: 10px; border: 1px solid #555; background: #000; margin-top: 10px;color: var(--text-primary);'>" + "<br><br>" + "<div style='text-align: center;'><button id='save-name' style='padding: 8px 25px; cursor: pointer;'>Accept</button></div>" ); Dialog.open(); $(document).one("click", "#save-name", function() { State.variables.chars.dataMap.get("you").name = $("#name-input").val(); Dialog.close(); Engine.play("Welcome_Curtain"); }); <</script>> <<silent>><<include "Game_Init">><</silent>><<set $gamestatus.stage = 1>> <</button>> </div> </div>
<style> .intro3content { position: relative; width: 100%; height: 100%; } .container { position: relative; width: 100%; height: 100%; overflow: hidden; } .container .image { position: absolute; top: 0; left: 0; width: 100%; height: 100%; background-image: [img[setup.ImagePath+'ui/bg/temple01.webp']]; background-size: cover; background-position: center; } #imgAwaken { opacity: 0; } </style> <div class="intro3content"> <div class="container"> <div class="image" id="imgAwaken"></div> </div> </div> <<run EffectEngine.playAwaken(setup.ImagePath + 'ui/bg/temple01.webp', () => { })>> <<timed 3s>> <<run $('#imgAwaken').css('opacity', 1);>> <<run Evm.flash('Event_Scene_Awake')>> <<run UIBar.show();>> <</timed>>
<<if Meta.totalGems > 0>> <<run setup.Popup(`<<AstralArchive>>`, { mandatory: true, buttons: [ { text: "Return", cancel: () => $.wiki(`<<goto "Welcome">>`) }, { text: "Awaken", confirm: () => $.wiki(`<<InheritData>><<goto "Welcome_Intro1">>`) }] });>> <<else>> <<goto "Welcome_Intro1">> <</if>>
<<widget TechUI_CatList>> <div id="techUI-tree-selector-tabs"></div> <</widget>> <<widget TechUI_TreeShow>> <<do tag "redoTechTree">><div class="techUI-tree-pointsbox">Points(Pts): <<ObjectList $tech.techPoint>></div><</do>> <<if _args[0]>> <<set _showCategory = _args[0]>> <<else>> <<set _showCategory = $tech.currentCategory>> <</if>> <<set _tracks = setup.TechTreeConfig.categories[_showCategory].tracks>> <div id="techUI-canvas"> <svg id="techUI-svg-layer"></svg> </div> <</widget>> <<widget TechUI_Dev>> <<button "Init">><<run setup.TechTreeEngine.init()>><</button>> <<button "Cheat">><<run cheatAddResources()>><</button>> <<button "Back">><<goto 'MainHall_UI'>><</button>> <<script>> <</script>> <</widget>>
<style> .dev-debugger-layout { display: flex; flex-direction: row; width: 100%; height: 100%; gap: 20px; background-color: #0b0c10; color: #c5c6c7; font-family: 'Consolas', monospace; padding: 10px; box-sizing: border-box; } .dev-viewport-panel { flex: 1; max-width: 768px; background: #1f2833; border: 2px solid #45f3ff; border-radius: 8px; position: relative; overflow: hidden; display: flex; justify-content: center; align-items: center; box-shadow: 0 0 15px rgba(69, 243, 255, 0.15); } #dev-portrait-display-container { width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; } .dev-control-panel { flex: 1.5; background: #121212; border: 1px solid #2d2d2d; border-radius: 8px; padding: 15px; display: flex; flex-direction: column; overflow-y: auto; } .dev-tab-header { display: flex; gap: 8px; border-bottom: 2px solid #1f2833; padding-bottom: 10px; margin-bottom: 15px; } .dev-tab-btn { background: #1f2833; border: 1px solid #45f3ff; color: #45f3ff; padding: 8px 16px; cursor: pointer; border-radius: 4px; font-weight: bold; transition: all 0.2s ease-in-out; } .dev-tab-btn:hover { background: #45f3ff; color: #0b0c10; } .dev-tab-btn.active { background: #45f3ff; color: #0b0c10; box-shadow: 0 0 10px rgba(69, 243, 255, 0.4); } .dev-tab-content { display: none; flex-direction: column; gap: 15px; } .dev-tab-content.active { display: flex; } .dev-card { background: #1c1c1e; border-radius: 6px; padding: 15px; border: 1px solid #2c2c2e; position: relative; } .dev-card h4 { margin-top: 0; margin-bottom: 12px; color: #45f3ff; border-left: 3px solid #45f3ff; padding-left: 8px; font-size: 1.1em; } .dev-input-group { display: flex; flex-direction: column; gap: 6px; margin-bottom: 12px; } .dev-input-group label { font-size: 0.85em; color: #8e8e93; text-transform: uppercase; } .dev-input-group select { background: #2c2c2e; border: 1px solid #3a3a3c; color: #fff; padding: 8px; border-radius: 4px; outline: none; font-family: inherit; cursor: pointer; } .dev-btn-row { display: flex; gap: 10px; margin-top: 15px; } .dev-btn { flex: 1; background: #2c2c2e; color: #fff; border: 1px solid #3a3a3c; padding: 10px; border-radius: 4px; cursor: pointer; font-weight: bold; font-family: inherit; text-align: center; transition: background 0.2s; } .dev-btn:hover { background: #3a3a3c; } .dev-btn.primary { background: #1f2833; border: 1px solid #45f3ff; color: #45f3ff; } .dev-btn.primary:hover { background: #45f3ff; color: #0b0c10; } .dev-specimen-metadata { display: flex; flex-direction: column; gap: 8px; font-size: 0.9em; } .dev-specimen-metadata strong { color: #45f3ff; } .dev-slider-group { margin-bottom: 15px; } .dev-slider-label { display: flex; justify-content: space-between; font-size: 0.9em; margin-bottom: 6px; } .dev-slider-label .subtext { font-size: 0.8em; color: #8e8e93; } .dev-slider-group input[type="range"] { width: 100%; background: #2c2c2e; height: 6px; border-radius: 3px; outline: none; cursor: pointer; } .dev-checkbox-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; } .dev-checkbox-container { display: flex; align-items: center; gap: 8px; cursor: pointer; user-select: none; } .dev-checkbox-container input { cursor: pointer; } .dev-selectors-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; max-height: 55vh; overflow-y: auto; padding-right: 5px; } .dev-selectors-grid::-webkit-scrollbar { width: 6px; } .dev-selectors-grid::-webkit-scrollbar-thumb { background-color: #3a3a3c; border-radius: 3px; } .dev-section-tip { font-size: 0.8em; color: #8e8e93; line-height: 1.4; margin-top: 0; margin-bottom: 15px; } .dev-palette-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; } .dev-color-box { display: flex; flex-direction: column; align-items: center; background: #2c2c2e; padding: 12px; border-radius: 6px; border: 1px solid #3a3a3c; } .dev-color-box label { font-size: 0.8em; margin-bottom: 8px; color: #8e8e93; } .dev-color-box input[type="color"] { border: none; width: 100%; height: 40px; background: transparent; cursor: pointer; } .hex-lbl { font-size: 0.8em; margin-top: 6px; color: #45f3ff; } .part-actual-val { color: #45f3ff; float: right; font-size: 0.9em; text-transform: none; } </style> <div class="dev-debugger-layout"> <div class="dev-viewport-panel"> <div id="dev-portrait-display-container"> <div id="dev-portrait-display"></div> <<do tag "lihuishuaxin">><<timed 0.2s>><<dynamicChar $debugNPC "dev-portrait-display" `{ bodyMorphs: { legWidth: 1.0 }, showClothes: true }`>><</timed>><</do>> </div> </div> <div class="dev-control-panel"> <div class="dev-tab-header"> <button class="dev-tab-btn active" onclick="PortraitDebugger.switchTab('factory')">Factory</button> <button class="dev-tab-btn" onclick="PortraitDebugger.switchTab('morphs')">Anatomy & Moods</button> <button class="dev-tab-btn" onclick="PortraitDebugger.switchTab('parts')">Part Swapper</button> <button class="dev-tab-btn" onclick="PortraitDebugger.switchTab('palette')">Palette Lab</button> </div> <div id="dev-tab-factory" class="dev-tab-content active"> <div class="dev-card"> <h4>Specimen Factory</h4> <div class="dev-input-group"> <label>Species (Race)</label> <select id="dev-race-select"></select> </div> <div class="dev-input-group"> <label>Evolutionary Rank (0-7)</label> <select id="dev-rank-select"> <option value="0">Rank 0</option><option value="1">Rank 1</option> <option value="2" selected>Rank 2</option><option value="3">Rank 3</option> <option value="4">Rank 4</option><option value="5">Rank 5</option> <option value="6">Rank 6</option><option value="7">Rank 7</option> </select> </div> <div class="dev-input-group"> <label>Gender Identification</label> <select id="dev-gender-select"> <option value="girl" selected>Girl</option> <option value="boy">Boy</option> <option value="futa">Futa</option> </select> </div> <div class="dev-checkbox-grid" style="margin-top:15px; border-top:1px solid #2d2d2d; padding-top:15px;"> <label class="dev-checkbox-container"> <input type="checkbox" id="chk-lock-colors" checked> <span style="color:#45f3ff; font-weight:bold;">[Lock] Keep Colors</span> </label> <label class="dev-checkbox-container"> <input type="checkbox" id="chk-lock-sliders" checked> <span style="color:#45f3ff; font-weight:bold;">[Lock] Keep Sliders</span> </label> </div> <div class="dev-btn-row"> <button class="dev-btn primary" onclick="PortraitDebugger.generateNewSpecimen()">Spawn Custom</button> <button class="dev-btn" onclick="PortraitDebugger.randomizeSpecimen()">Random Specimen</button> <<button "Hard Refresh">><<redo "lihuishuaxin">><</button>> </div> </div> <div class="dev-card"> <h4>Identity Card</h4> <div class="dev-specimen-metadata"> <div><strong>Name:</strong> <span id="meta-name">--</span></div> <div><strong>ID:</strong> <span id="meta-id">--</span></div> <div><strong>Genome Ref:</strong> <span id="meta-ref">--</span></div> </div> </div> </div> <div id="dev-tab-morphs" class="dev-tab-content"> <div class="dev-card"> <h4>Mood Vector Matrix</h4> <div class="dev-slider-group"> <div class="dev-slider-label"> <span>Valence (vl): <span id="val-vl">50</span></span> <span class="subtext">Gloom (<50) | Joy (>50)</span> </div> <input type="range" id="slider-vl" min="0" max="100" value="50" oninput="PortraitDebugger.onSliderChange('vl', this.value)"> </div> <div class="dev-slider-group"> <div class="dev-slider-label"> <span>Personality / Arousal: <span id="val-arousal">50</span></span> </div> <input type="range" id="slider-arousal" min="0" max="100" value="50" oninput="PortraitDebugger.onSliderChange('arousal', this.value)"> </div> </div> <div class="dev-card"> <h4>Anatomical Scale Adjustments</h4> <div class="dev-slider-group"> <div class="dev-slider-label"> <span>Leg / Hip Width: <span id="val-legWidth">1.0</span></span> </div> <input type="range" id="slider-legWidth" min="0.5" max="2.0" step="0.05" value="1.0" oninput="PortraitDebugger.onSliderChange('legWidth', this.value)"> </div> <div class="dev-slider-group"> <div class="dev-slider-label"> <span>Height: <span id="val-height">160</span> cm</span> </div> <input type="range" id="slider-height" min="100" max="220" value="160" oninput="PortraitDebugger.onSliderChange('height', this.value)"> </div> <div class="dev-slider-group"> <div class="dev-slider-label"> <span>Pregnancy Stage (preg): <span id="val-preg">0</span></span> </div> <input type="range" id="slider-preg" min="0" max="5" value="0" oninput="PortraitDebugger.onSliderChange('preg', this.value)"> </div> </div> <div class="dev-card"> <h4>Render Flags</h4> <div class="dev-checkbox-grid"> <label class="dev-checkbox-container"> <input type="checkbox" id="chk-showClothes" checked onchange="PortraitDebugger.onCheckboxChange('showClothes', this.checked)"> <span>Render Clothing</span> </label> <label class="dev-checkbox-container"> <input type="checkbox" id="chk-showCum" onchange="PortraitDebugger.onCheckboxChange('showCum', this.checked)"> <span>Fluid Overlays (Cum)</span> </label> <label class="dev-checkbox-container"> <input type="checkbox" id="chk-isErect" onchange="PortraitDebugger.onCheckboxChange('isErect', this.checked)"> <span>Erect Phallus</span> </label> </div> </div> </div> <div id="dev-tab-parts" class="dev-tab-content"> <div class="dev-card"> <h4>Manual Part Overrides</h4> <p class="dev-section-tip">Select components directly from memory. Modifying these locks the selected frame against automatic genome updates. "Auto" follows genome defaults.</p> <div id="dev-part-selectors-container" class="dev-selectors-grid"> <!-- Injected by JS --> </div> </div> </div> <div id="dev-tab-palette" class="dev-tab-content"> <div class="dev-card"> <h4>Hex Tint Lab</h4> <p class="dev-section-tip">Changes colors in real time. We extract hex to WebGL normalize scale float values.</p> <div class="dev-palette-grid"> <div class="dev-color-box"> <label>Skin Tone</label> <input type="color" id="color-skin" value="#ffffff" oninput="PortraitDebugger.onColorChange('skin', this.value)"> <span id="hex-skin" class="hex-lbl">#ffffff</span> </div> <div class="dev-color-box"> <label>Fur / Hair</label> <input type="color" id="color-fur" value="#ffffff" oninput="PortraitDebugger.onColorChange('fur', this.value)"> <span id="hex-fur" class="hex-lbl">#ffffff</span> </div> <div class="dev-color-box"> <label>Iris Hue</label> <input type="color" id="color-iris" value="#ffffff" oninput="PortraitDebugger.onColorChange('iris', this.value)"> <span id="hex-iris" class="hex-lbl">#ffffff</span> </div> </div> </div> </div> </div> </div> <<done>> <<script>> window.PortraitDebugger = { npc: null, options: { showClothes: true, showCum: false, isErect: false, legWidth: 1.0 }, _atlasDiscoveryMap: {}, _partsList: [ "face", "mouth", "brow", "beard", "hairFront", "hairBack", "ear", "wing", "tail", "body", "leg", "arm", "eyeSclera", "eyesIris", "eyesLid", "breasts", "belly", "cock", "clothesSet" ], init: function() { if (!State.variables.debugNPC) { const defaultRace = this._getFirstAvailableRace() || "allureborn"; State.variables.debugNPC = createMonster(defaultRace, 3, "girl"); } this.npc = State.variables.debugNPC; this._buildAtlasDiscoveryMap(); this._registerNPCPartsToDiscovery(this.npc); this._populateRaceSelector(); this._buildPartSelectors(); setTimeout(() => { this.syncNpcToUI(); this.adjustCanvasSize(); }, 50); }, switchTab: function(tabName) { document.querySelectorAll('.dev-tab-btn').forEach(btn => btn.classList.remove('active')); document.querySelectorAll('.dev-tab-content').forEach(content => content.classList.remove('active')); Array.from(document.querySelectorAll('.dev-tab-btn')).find(btn => btn.getAttribute('onclick').includes(tabName))?.classList.add('active'); document.getElementById(`dev-tab-${tabName}`)?.classList.add('active'); }, generateNewSpecimen: function() { const race = document.getElementById('dev-race-select').value; const rank = parseInt(document.getElementById('dev-rank-select').value); const gender = document.getElementById('dev-gender-select').value; const newNPC = createMonster(race, rank, gender); this._processSpawning(newNPC); }, randomizeSpecimen: function() { const newNPC = createMonster(); this._processSpawning(newNPC); }, _processSpawning: function(newNPC) { this._registerNPCPartsToDiscovery(newNPC); const isLockColors = document.getElementById('chk-lock-colors').checked; const isLockSliders = document.getElementById('chk-lock-sliders').checked; if (isLockColors) { newNPC.atlas.colors.skin = document.getElementById('color-skin').value; newNPC.atlas.colors.fur = document.getElementById('color-fur').value; newNPC.atlas.colors.iris = document.getElementById('color-iris').value; } if (isLockSliders) { newNPC.mood.vl = parseFloat(document.getElementById('slider-vl').value); newNPC.mood.personality = parseFloat(document.getElementById('slider-arousal').value); newNPC.height = parseInt(document.getElementById('slider-height').value); newNPC.preg = parseInt(document.getElementById('slider-preg').value); } this._partsList.forEach(part => { const select = document.getElementById(`dev-part-select-${part}`); if (select) { const val = select.value; if (!newNPC.atlas.parts[part]) newNPC.atlas.parts[part] = {}; if (val === 'none') { newNPC.atlas.parts[part].frame = null; } else if (val !== 'auto' && val !== '') { const [atlasKey, frameName] = val.split('|'); newNPC.atlas.parts[part].frame = frameName; newNPC.atlas.parts[part].atlas = atlasKey === 'null' ? null : atlasKey; } } }); State.variables.debugNPC = newNPC; this.npc = newNPC; this._buildPartSelectors(); this.syncNpcToUI(); this.applyChanges(); }, _buildAtlasDiscoveryMap: function() { this._atlasDiscoveryMap = {}; const standalones = { body: ["body_girl", "body_boy"], tail: ["tail_canine", "tail_feline", "tail_allureborn", "tail_avian", "tail_bovine", "tail_equine", "tail_reptile"], wing: ["wings_allureborn", "wings_allureborn_S", "wings_avian"], breasts: ["breasts_A", "breasts_B", "breasts_C", "breasts_D", "breasts_E", "breasts_F", "breasts_G", "breasts_m"], cock: ["cock_H"], belly: ["belly_E"], eyesLid: ["eyelid_girl", "eyelid_boy"] }; this._partsList.forEach(part => { this._atlasDiscoveryMap[part] = []; if (standalones[part]) { standalones[part].forEach(name => { this._atlasDiscoveryMap[part].push({ frame: name, atlas: 'null' }); }); } if (setup.atlasData) { for (let [atlasKey, atlas] of Object.entries(setup.atlasData)) { const atlasLower = atlasKey.toLowerCase(); const partLower = part.toLowerCase(); let isMatch = false; if (part === "clothesSet") isMatch = atlasLower.startsWith("cloth_"); else if (["eyesIris", "eyeSclera", "eyesLid"].includes(part)) isMatch = atlasLower.includes("eye"); else if (["hairFront", "hairBack"].includes(part)) isMatch = atlasLower.includes("hair"); else isMatch = atlasLower.includes(partLower); if (isMatch && atlas.frames) { for (let frameName of Object.keys(atlas.frames)) { this._atlasDiscoveryMap[part].push({ frame: frameName, atlas: atlasKey }); } } } } }); }, _registerNPCPartsToDiscovery: function(npc) { if(!npc || !npc.atlas || !npc.atlas.parts) return; for(let [partKey, partData] of Object.entries(npc.atlas.parts)) { if(partData && partData.frame) { if(!this._atlasDiscoveryMap[partKey]) this._atlasDiscoveryMap[partKey] = []; const atlasVal = partData.atlas || 'null'; const exists = this._atlasDiscoveryMap[partKey].some(c => c.frame === partData.frame && c.atlas === atlasVal); if(!exists) { this._atlasDiscoveryMap[partKey].push({ frame: partData.frame, atlas: atlasVal }); } } } }, _populateRaceSelector: function() { const select = document.getElementById('dev-race-select'); if (!select) return; select.innerHTML = ''; const races = setup.bestiaryDB ? Object.keys(setup.bestiaryDB) : ["allureborn", "canine"]; races.forEach(race => { const opt = document.createElement('option'); opt.value = race; opt.innerText = race.charAt(0).toUpperCase() + race.slice(1); if (this.npc && this.npc.race === race) opt.selected = true; select.appendChild(opt); }); }, _buildPartSelectors: function() { const container = document.getElementById('dev-part-selectors-container'); if (!container) return; const currentSelections = {}; this._partsList.forEach(part => { const el = document.getElementById(`dev-part-select-${part}`); if (el) currentSelections[part] = el.value; }); container.innerHTML = ''; this._partsList.forEach(part => { const group = document.createElement('div'); group.className = 'dev-input-group'; const label = document.createElement('label'); label.innerHTML = `${part} <span id="actual-part-${part}" class="part-actual-val"></span>`; group.appendChild(label); const select = document.createElement('select'); select.id = `dev-part-select-${part}`; select.onchange = () => this.applyManualPartOverride(part); const autoOpt = document.createElement('option'); autoOpt.value = 'auto'; autoOpt.innerText = '[ Auto (Genome Default) ]'; select.appendChild(autoOpt); const noneOpt = document.createElement('option'); noneOpt.value = 'none'; noneOpt.innerText = '[ Hide (Force None) ]'; select.appendChild(noneOpt); const candidates = this._atlasDiscoveryMap[part] || []; candidates.forEach(cand => { const opt = document.createElement('option'); opt.value = `${cand.atlas}|${cand.frame}`; opt.innerText = `${cand.frame} [${cand.atlas}]`; select.appendChild(opt); }); if (currentSelections[part]) { select.value = currentSelections[part]; } else { select.value = 'auto'; } group.appendChild(select); container.appendChild(group); }); }, applyManualPartOverride: function(part) { if (!this.npc) return; const select = document.getElementById(`dev-part-select-${part}`); if (!select) return; if (!this.npc.atlas.parts[part]) { this.npc.atlas.parts[part] = {}; } const val = select.value; if (val === 'auto') { const dummy = createMonster(this.npc.race, this.npc.rank, this.npc.gender); this.npc.atlas.parts[part] = dummy.atlas.parts[part] ? { ...dummy.atlas.parts[part] } : { frame: null, atlas: null }; } else if (val === 'none') { this.npc.atlas.parts[part].frame = null; } else { const [atlasKey, frameName] = val.split('|'); this.npc.atlas.parts[part].frame = frameName; this.npc.atlas.parts[part].atlas = atlasKey === 'null' ? null : atlasKey; } this.syncNpcToUI(); this.applyChanges(); }, syncNpcToUI: function() { if (!this.npc) return; document.getElementById('meta-name').innerText = this.npc.name || "Unnamed"; document.getElementById('meta-id').innerText = this.npc.id || "0"; document.getElementById('meta-ref').innerText = `${this.npc.race} [Rank ${this.npc.rank}] | Gender: ${this.npc.gender}`; document.getElementById('slider-vl').value = this.npc.mood?.vl ?? 50; document.getElementById('val-vl').innerText = Math.trunc(this.npc.mood?.vl ?? 50); document.getElementById('slider-arousal').value = this.npc.mood?.personality ?? 50; document.getElementById('val-arousal').innerText = Math.trunc(this.npc.mood?.personality ?? 50); document.getElementById('slider-height').value = this.npc.height || 160; document.getElementById('val-height').innerText = this.npc.height || 160; document.getElementById('slider-preg').value = this.npc.preg || 0; document.getElementById('val-preg').innerText = this.npc.preg || 0; const raceSelect = document.getElementById('dev-race-select'); if (raceSelect) raceSelect.value = this.npc.race; const rankSelect = document.getElementById('dev-rank-select'); if (rankSelect) rankSelect.value = this.npc.rank; const genderSelect = document.getElementById('dev-gender-select'); if (genderSelect) genderSelect.value = this.npc.gender; this._partsList.forEach(part => { const partData = this.npc.atlas.parts[part]; const actualSpan = document.getElementById(`actual-part-${part}`); if (actualSpan) { actualSpan.innerText = partData && partData.frame ? `(${partData.frame})` : `(hidden)`; } }); const colorTypes = ['skin', 'fur', 'iris']; colorTypes.forEach(type => { let colorVal = this.npc.atlas.colors[type]; const hexColor = this._normalizeToHex(colorVal); const picker = document.getElementById(`color-${type}`); if (picker) picker.value = hexColor; const label = document.getElementById(`hex-${type}`); if (label) label.innerText = hexColor; }); }, onSliderChange: function(type, val) { if (!this.npc) return; if (type === 'vl') { this.npc.mood.vl = parseFloat(val); document.getElementById('val-vl').innerText = Math.trunc(val); } else if (type === 'arousal') { this.npc.mood.personality = parseFloat(val); document.getElementById('val-arousal').innerText = Math.trunc(val); } else if (type === 'legWidth') { this.options.legWidth = parseFloat(val); document.getElementById('val-legWidth').innerText = val; } else if (type === 'height') { this.npc.height = parseInt(val); document.getElementById('val-height').innerText = val; this.adjustCanvasSize(); } else if (type === 'preg') { this.npc.preg = parseInt(val); document.getElementById('val-preg').innerText = val; } this.applyChanges(); }, onCheckboxChange: function(type, checked) { this.options[type] = checked; this.applyChanges(); }, onColorChange: function(type, hexValue) { if (!this.npc) return; this.npc.atlas.colors[type] = hexValue; const label = document.getElementById(`hex-${type}`); if (label) label.innerText = hexValue; this.applyChanges(); }, adjustCanvasSize: function() { const wrapper = document.getElementById('dev-portrait-display-container'); if (!wrapper || !this.npc) return; const npcHeight = this.npc.height || 160; const targetH = Math.min(Math.max((npcHeight * 3.75 + 128), 256), window.innerHeight * 0.85); wrapper.style.height = `${targetH}px`; }, applyChanges: function() { if (!this.npc) return; const containerId = "dev-portrait-display"; const renderer = AvatarSystem.activeInstances.get(containerId); if (!renderer) return; AvatarSystem.SetPartToAtlas.transform(this.npc); const layers = AvatarSystem.Assembler.build(this.npc, { bodyMorphs: { legWidth: this.options.legWidth }, showClothes: this.options.showClothes, showCum: this.options.showCum, isErect: this.options.isErect }); renderer.setLayers(layers).then(() => { if (renderer.gl && !renderer.gl.isContextLost() && !renderer.isRunning) { renderer.start(); } }).catch(console.error); }, _normalizeToHex: function(val) { if (!val) return "#ffffff"; if (typeof val === 'string') { if (val.startsWith('#')) return val; if (window.GameColorUtils && typeof GameColorUtils.parseColor === 'function') { const parsed = GameColorUtils.parseColor(val); if (Array.isArray(parsed) && parsed.length >= 3) { return this._rgbArrayToHex(parsed); } } } if (Array.isArray(val) && val.length >= 3) { return this._rgbArrayToHex(val); } return "#ffffff"; }, _rgbArrayToHex: function(arr) { const r = Math.min(255, Math.max(0, Math.round(arr[0] * 255))).toString(16).padStart(2, '0'); const g = Math.min(255, Math.max(0, Math.round(arr[1] * 255))).toString(16).padStart(2, '0'); const b = Math.min(255, Math.max(0, Math.round(arr[2] * 255))).toString(16).padStart(2, '0'); return `#${r}${g}${b}`; }, _getFirstAvailableRace: function() { if (setup.bestiaryDB) { const keys = Object.keys(setup.bestiaryDB); if (keys.length > 0) return keys[0]; } return null; } }; PortraitDebugger.init(); <</script>> <</done>>
<<set $chars.dataMap.get("you").wil.op = 0>> <<run console.log(clone($chars.dataMap.get("you")))>> <<set $chars.dataMap.get("you").addTrait("Fertile", 5)>> <<set $chars.dataMap.get("you").feedItem("dexPotion", 5)>> <<run $chars.dataMap.get("you").getFinalStats()>> <<run console.log(clone($chars.dataMap.get("you")))>> <<run $chars.dataMap.get("you").getFinalStats()>> <<run console.log(clone($chars.dataMap.get("you")))>> <<run console.log(setup.updateGenesToAttrs($chars.dataMap.get("you")))>>
<<do tag "exp">> <<run console.log($chars.dataMap.get("catmaid"))>> ee<<= $chars.dataMap.get("catmaid").genes.mouth.status.experience>> exp<<= $chars.dataMap.get("you").sta.exp>> lv<<= $chars.dataMap.get("you").sta.lv>> %<<= $chars.dataMap.get("you").progressText("level")>> <</do>> <<button "addexp">> <<run trainAction($chars.dataMap.get("you"), $chars.dataMap.get("catmaid"), "deep_kiss")>> <<redo "exp">><</button>>
<<set $chars.dataMap.get("you").wil.lv = 20 >> <<set $chars.dataMap.get("you").stamina.current = 200 >> <<set $chars.dataMap.get("catmaid").stamina.current = 20 >> <<set $chars.dataMap.get("mirael").stamina.current = 20 >> <<set sv.manage.energy.current += 5000>> <<run randomDispatch()>><<run randomDispatch()>><<run randomDispatch()>> <<set $domain.maps[1] = "Map_Vale02" >> <<run changeEffects(0, 1, "bountiful_fields")>> <<pickup $storage "gold" 1000000 "wood" 50000 "stone" 10000 "ore" 10000 "crystal" 1000>> <<pickup $storage "FelineFurball-1" 4 "BovineMilk-10" 5>> <<pickup $storage "EquineMane-3" 4 "CanineFang-5" 5>> <<pickup $storage "AvianPlume-10" 4 "AllurebornTears-9" 5>> <<pickup $storage "potentialPotion-1" 4 "appPotion-5" 5>> <<pickup $storage "uncommonPotion-1" 4 "appPotion-5" 5>> <<pickup $storage "hapPotion-3" 4 "appPotion-5" 5>> <<pickup $storage "hapPotion-3" 4 "appPotion-5" 5>> <<run setup.addSpeciesRank("feline", 3)>> <<run setup.addSpeciesRank("canine", 3)>> <<run setup.addSpeciesRank("equine", 3)>> <<run setup.addSpeciesRank("bovine", 3)>> <<run setup.addSpeciesRank("reptile", 3)>> <<run setup.addSpeciesRank("avian", 3)>> <<run setup.addSpeciesRank("allureborn", 3)>> <<run setup.addSpeciesRank("sirenia", 3)>> <<run sv.tech.enTraits.pushUnique("revgender")>> <<run sv.tech.enTraits.pushUnique("dualgender")>> <<run sv.tech.enTraits.pushUnique("heartforge")>>
<<button "Test段落">><<goto '0所有Test段落'>><</button>> <<= "tutorial "+$Flags.tutorial>><br> <<= "mirael "+$Flags.miraelflag>><br> <<= "catmaid "+$Flags.catmaidflag>><br> <<= "leah "+$Flags.leahflag>><br> <<= "butle "+$Flags.butleflag>><br> <<= "scalemaid "+$Flags.scalemaidflag>><br> <<= "MessageShow "+$messages.isMessageShowing>><br> <<= "FastMessage "+$messages.isFastMessageShowing>><br> <<= "messageQueue "+ $messages.messageQueue.length>><br> <<= "messageFast "+$messages.messageFast.length>><br> <<= "stage "+$gamestatus.stage>><br><<button "+">><<set $gamestatus.stage += 1 >><</button>><<button "-">><<set $gamestatus.stage -= 1 >><</button>> <div class="uibar-button-group"> <div class="uibar-button-row debug-sidebutton" style="text-align: left;"> <<= "now: "+ passage()>> <br>功能链接<br> <<button "存档">><<run UI.saves()>><</button>> <<button "Menu🖥️">><<goto "Welcome">><</button>> <<button "科技树">><<goto 'TechTree_UI'>><</button>> <br>变量控制<br> <<button "跳过1天">><<run setup.TimeSystem.passOneDay();>><</button>> <<button "回退1天">><<set $times.total -= 1 >><</button>> <<button "开启战斗">><<SetFlag 'battleday'>><</button>> <<button "页面高度">><<run alert(document.body.offsetHeight);>><</button>> <<button 'TestMode'>><<SetFlag 'debug'>><<<<include "0TestMode">><<run UI.update();>><</button>> <br>存档数据查看<br> <<button "仓库(storage)">><<run console.table($storage.list)>><</button>> <<button "召唤(summon)">><<run console.table($summon)>><</button>> <<button "日志(codex)">><<run console.table($gamestatus.codexdata)>><</button>> <<button "领地(domain)">><<run console.table($domain)>><</button>> <<button "黑市(market)">><<run console.table($market)>><</button>> <<button "管理(manage)">><<run console.table($manage)>><</button>> <<button "建筑(build)">><<run console.table($buildings)>><</button>> <<button "建筑效果">><<run console.table($buildingEffects)>><</button>> <<button "奴隶(slave)">><<run console.table($slave)>><</button>> <<button "角色(chars)">><<run console.table($chars)>><</button>> <<button "派遣(dispatch)">><<run console.table($dispatch)>><</button>> <<button "任务(Quest)">><<run console.table($questState);>><</button>> <<button "Meta(元素)">><<run console.table(State.metadata.entries());>><</button>> <br>功能模块查看<br> <<button "当前选择">><<run console.table($chars.selected)>><</button>> <br>自动生成数据<br> <<button "等级表">><<run console.table(setup.statLevel.table)>><</button>> <<button "codex类">><<run console.table(setup.codexcategory)>><</button>> <<button "头像缓存">><<run console.table(setup.HeadPortraitCache)>><</button>> <br>调试模块<br> <<button "新增派遣">><<run randomDispatch()>><</button>> <<button '刷新市场' Market_UI>><<run newMarket()>><</button>> <<button "解锁灌注">><<pickup $storage "Basic_Infuser_Core" 1>><<run EventBus.emit("tech:requestUnlock", "infuser1");>><</button>> <<button "解锁DT">><<run EventBus.emit("action:unlock", { actionId: "deepthroat" });>><</button>> <br>临时运行<br> <<button "猫女游侠模板">><<set _mStandard = setup.createStandardMonster("feline","feline_catfolk",2,"girl"); >><<run console.log(_mStandard)>><</button>> <<button "chaPotion">><<pickup $storage "chaPotion-1" 4 "chaPotion-10" 5 "AvianPlume-10" 10>><</button>> <<button "进入140天">><<set $times.total = 140 >><</button>> <<button "唤醒事件">><<run Evm.hook("daily_morning")>><</button>> <<button "处理事件">><<run Evm.show()>><</button>> <<button "立即收税">><<set $domain.wartaxdeadline = 0>><<set $Flags['wartaxs'] = true >><</button>> <<button "解锁女神科技">><<run sv.tech.enTrees.pushUnique("goddess"); sv.manage.energy.current += 1000;>><</button>> <<button "解锁成就">><<run Meta.unlockAch("spend_1m")>><</button>> <<button "注入270">><<set $manage.preg.infuserDays += 270 >><</button>> <<button "Mirael Birth">><<run $chars.dataMap.get("mirael").giveBirth()>><</button>> <<button "背包unmerge">><<run $storage.unmerge({gold:10000, food:10, wood:5})>><</button>> <<button "notify">><<run Msg.notify("Unlock [Hawkridge Vale] Map")>><</button>> <<button "job">><<goto "Job_UI">><</button>> <<button "增加expedition_camp">><<run changeEffects(0, 1, "expedition_camp")>><</button>> <br>事件任务<br> <<button "事件管理">><<run setup.EventManager.debug()>><</button>> <<button "事件总线">><<run EventBus.debug();>><</button>> <<button "Flag总览">><<run console.table($Flags);>><</button>> <<button "时间times">><<run console.table($times)>><</button>> <<button "消息队列">><<run console.table($messages.messageQueue)>><</button>> <<button "飞入消息">><<run console.table($messages.messageFast)>><</button>> <br>图鉴相关<br> <<button "goto图鉴">><<goto Bestiary>><</button>> <<button "bestiaryDB">><<run console.table(setup.bestiaryDB)>><</button>> <<button "lineageDB">><<run console.table(setup.lineageDB)>><</button>> <<button "speciesDB">><<run console.table(setup.speciesDB)>><</button>> <<button "sv.bestiary">><<run console.table($bestiary)>><</button>> <br>科技树<br> <<button "科技(tech)">><<run console.table($tech)>><</button>> <div> <span> 预览状态: <<print window.location.pathname.includes("index_md.html") ? "N" : "S">> </span> <<button "S">> <<run localStorage.setItem("dev_preview_mode", "S")>> <<run window.location.replace(window.location.href.replace("index_md.html", "index_bd.html"))>> <</button>> <<button "N">> <<run localStorage.setItem("dev_preview_mode", "N")>> <<run window.location.replace(window.location.href.replace("index_bd.html", "index_md.html"))>> <</button>> </div> </div> </div> <hr>
<<cgList "interaction" "mirael">>
获取物品多语言名称<br> <<= setup.resourcesdata.wood.name["en"] >> <br> ----------------------------------- 消耗品效果测试:<br> <<link "喝一瓶">> <<run $chars.dataMap.get("you").feedItem("BovineMilk", 5)>> <<redo>> <</link>> <<do>> <<= $chars.dataMap.get("you").giveFer>> <</do>> <hr> <<= setup.harvestdata.BovineMilk.name["en"] >>
<<button "地图">> <style> #ui-dialog { max-width: 1920px; width: 75%; } .mappopup{ left:5rem; } </style> <<run Dialog.create("Map", "mappopup");>> <<run Dialog.wikiPassage("MapsPopup") >> <<run Dialog.open()>> <</button>> <<set $domain.maps[1] = "Map_Vale02" >>
<<set $a = 0.4>> <<set $b = 0.5>> <<set $c = $a+$b>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>> <<print lottery($a, $b, $c)>>
$dispatch.quests.length <<set _newquest = new Quest({})>> <<= _newquest.randomprop("sta",10);>> <<run $dispatch.quests.push(_newquest);>> <<set _newquest = new Quest({})>> <<= _newquest.randomreqtraits(1,"taskexpert",1);>> <<run $dispatch.quests.push(_newquest);>> <<set _newquest = new Quest({})>> <<= _newquest.randomprop("sta",10);>> <<= _newquest.randomreqtraits(1,"taskexpert",1);>> <<run $dispatch.quests.push(_newquest);>> <<run randomDispatch()>><<run randomDispatch()>><<run randomDispatch()>> <<run randomDispatch()>><<run randomDispatch()>><<run randomDispatch()>>
<<timed 1s>><<= coupledata($chars.dataMap.get("you"), $chars.dataMap.get("catmaid"));>><</timed>> <p id="demo"></p> <<link "检查图库状态">> <<run checkMissing()>> <</link>>
<div style="width:600px;height:400px;background:orange;color:var(--text-inverse);"> 周几<<= $times.currentWeekday >> 几号<<= $times.currentDate.day >> 几月<<= $times.currentDate.month >> 几年<<= $times.currentDate.year >> <<link "过去一天">><<run setup.TimeSystem.passOneDay();>><<redo>><</link>> <<do>><<= $times.getCurrentInfo(); >> $times.currentDate.day <</do>> </div>
<div style="background: #1a1a3a; padding: 20px; border: 2px solid #D4AF37; border-radius: 10px; max-width: 500px; margin: 20px auto; color: white;"> <h3 style="color: #D4AF37; text-align: center; margin-top: 0;">🛠️ 开发者工具: 福利码指纹生成器</h3> <div style="margin-bottom: 10px;"> <label>新福利码 (如 Patreon2604, 经常更新):</label><br> <input type="text" id="dev-code" style="width: 100%; padding: 5px; background: #333; color: white; border: 1px solid #777;"> </div> <div style="margin-bottom: 10px;"> <label>当前的 Salt (默认 AxiomNova, 尽量不要改):</label><br> <input type="text" id="dev-salt" value="AxiomNova" style="width: 100%; padding: 5px; background: #333; color: white; border: 1px solid #777;"> </div> <button onclick="calcDevHash()" style="width: 100%; padding: 10px; background: #D4AF37; color: var(--text-inverse); font-weight: bold; border: none; cursor: pointer; border-radius: 5px;"> 生成 Target Hash </button> <div style="margin-top: 20px;"> <label style="color: #4CAF50;">请将此 Hash 复制到 setup.PatreonSystem.targetHash 中:</label> <textarea id="dev-result" readonly style="width: 100%; height: 60px; margin-top: 5px; background: #000; color: #4CAF50; border: 1px solid #4CAF50; word-break: break-all;"></textarea> </div> </div> <<done>><<script>> window.calcDevHash = async function() { const code = document.getElementById('dev-code').value.trim(); const salt = document.getElementById('dev-salt').value.trim(); if (!code) { alert("福利码不能为空!"); return; } const hash = await setup.PatreonSystem.generateHash(code, salt); document.getElementById('dev-result').value = hash; } <</script>><</done>>
<style> #debug-console { position: fixed; top: 10px; right: 10px; width: 300px; background: rgba(18, 18, 24, 0.95); border: 1px solid #444; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.5); z-index: 9999; font-family: Consolas, monospace; color: #eee; display: flex; flex-direction: column; overflow: hidden; transition: transform 0.3s ease; } #debug-console.minimized { transform: translateX(260px); height: 40px; } .debug-header { background: #2c3e50; padding: 8px 12px; font-size: 12px; font-weight: bold; display: flex; justify-content: space-between; align-items: center; cursor: pointer; user-select: none; border-bottom: 1px solid #444; } .debug-status { color: #2ecc71; font-size: 10px; } .debug-body { padding: 10px; max-height: 80%; overflow-y: auto; } .debug-section { margin-bottom: 12px; border-bottom: 1px solid #333; padding-bottom: 8px; } .debug-section-title { font-size: 11px; color: #aaa; margin-bottom: 6px; text-transform: uppercase; letter-spacing: 1px; } .debug-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; } .debug-btn { background: #34495e; border: 1px solid #455a64; color: #fff; padding: 4px 8px; font-size: 11px; cursor: pointer; border-radius: 4px; text-align: center; transition: background 0.2s; } .debug-btn:hover { background: #3498db; } .debug-btn:active { transform: translateY(1px); } .debug-btn.danger { border-color: #c0392b; color: #e74c3c; } .debug-btn.danger:hover { background: #c0392b; color: #fff; } .debug-info-row { font-size: 11px; margin-bottom: 4px; display: flex; justify-content: space-between; } .debug-val { color: #f1c40f; } </style> <<script>> class DebugPanel { constructor() { this.state = State.variables.bestiary; this.currentId = null; this.container = null; this.init(); setInterval(() => this.refreshInfo(), 1000); } init() { const div = document.createElement('div'); div.id = 'debug-console'; div.innerHTML = ` <div class="debug-header" onclick="this.parentElement.classList.toggle('minimized')"> <span>🛠️ GM CONSOLE</span> <span class="debug-status" id="debug-target-id">None</span> </div> <div class="debug-body"> <!-- Global Actions --> <div class="debug-section"> <div class="debug-section-title">Global</div> <div class="debug-grid"> <button class="debug-btn" onclick="window.DebugPanel.unlockAll()">Unlock All(WIP)</button> <button class="debug-btn danger" onclick="window.DebugPanel.resetAll()">Reset Data</button> </div> </div> <!-- Target Actions --> <div class="debug-section"> <div class="debug-section-title">Current Target</div> <div class="debug-info-row">ID: <span class="debug-val" id="dbg-id">---</span></div> <div class="debug-info-row">Status: <span class="debug-val" id="dbg-status">---</span></div> <div class="debug-section-title mt-2">Status Override</div> <div class="debug-grid"> <button class="debug-btn" onclick="window.DebugPanel.setStatus('sighted')">👁️ Sighted</button> <button class="debug-btn" onclick="window.DebugPanel.setStatus('captured')">⛓️ Captured</button> <button class="debug-btn" onclick="window.DebugPanel.setStatus('contracted')">💍 Contract</button> </div> </div> <!-- Interaction Simulation --> <div class="debug-section"> <div class="debug-section-title">Train / Interact</div> <div class="debug-grid"> <button class="debug-btn" onclick="window.DebugPanel.doAction('mouth', 1)">mouth (+1)</button> <button class="debug-btn" onclick="window.DebugPanel.doAction('hand', 1)">hand (+1)</button> <button class="debug-btn" onclick="window.DebugPanel.doAction('pussy', 1)">pussy (+1)</button> <button class="debug-btn" onclick="window.DebugPanel.doAction('cock', 1)">cock (+1)</button> <button class="debug-btn" onclick="window.DebugPanel.doAction('tail', 1)">tail (+1)</button> <button class="debug-btn" onclick="window.DebugPanel.doAction('ass', 1)">ass (+1)</button> <button class="debug-btn" onclick="window.DebugPanel.doAction('wing', 1)">wing (+1)</button> <button class="debug-btn" onclick="window.DebugPanel.doAction('lowerbody', 1)">lowerbody (+1)</button> <button class="debug-btn" onclick="window.DebugPanel.doAction('breast', 1)">breast (+1)</button> <button class="debug-btn" onclick="window.DebugPanel.doAction('summonCount', 1)">Summon (+1)</button> <button class="debug-btn" onclick="window.DebugPanel.doAction('climaxCount', 1)">💦 Climax (+1)</button> <button class="debug-btn" onclick="window.DebugPanel.doAction('cum_inside', 100)">🥛 Creampie (+100)</button> <button class="debug-btn" onclick="window.DebugPanel.doAction('defeatCount', 1)">⚔️ Defeat (+1)</button> <button class="debug-btn" onclick="window.DebugPanel.doAction('intimacy', 10)">❤️ Intimacy (+10)</button> </div> </div> <!-- Flag Toggles --> <div class="debug-section"> <div class="debug-section-title">Events / Flags</div> <div class="debug-grid"> <button class="debug-btn" onclick="window.DebugPanel.toggleFlag('broken')">💔 Break Mind</button> <button class="debug-btn" onclick="window.DebugPanel.toggleFlag('pregnancy')">🤰 Impreg</button> </div> </div> <!-- Other Toggles --> <div class="debug-section"> <div class="debug-section-title">Other</div> <div class="debug-grid"> <button class="debug-btn" onclick="window.DebugPanel.toggleFlag('broken')">💔 Break Mind</button> <button class="debug-btn" onclick='EventBus.emit("origin:unlock", { id: "primal" });'>🤰 unlock primal</button> <button class="debug-btn" onclick="window.DebugPanel.toggleFlag('pregnancy')">🤰 Impreg</button> <button class="debug-btn" onclick="window.DebugPanel.toggleFlag('pregnancy')">🤰 Impreg</button> <button class="debug-btn" onclick="window.DebugPanel.toggleFlag('pregnancy')">🤰 Impreg</button> <button class="debug-btn" onclick="window.DebugPanel.toggleFlag('pregnancy')">🤰 Impreg</button> <button class="debug-btn" onclick="window.DebugPanel.toggleFlag('pregnancy')">🤰 Impreg</button> </div> </div> </div> `; $("#game-app-container").append(div); this.dom = { id: div.querySelector('#dbg-id'), status: div.querySelector('#dbg-status'), targetLabel: div.querySelector('#debug-target-id') }; window.DebugPanel = this; } getCurrentTarget() { const lastSel = State.variables.bestiary.ui.lastSelectedId; if (lastSel && lastSel.type === 'species') { return lastSel.id; } return null; } refreshInfo() { const id = this.getCurrentTarget(); this.currentId = id; if (!id) { this.dom.id.textContent = "---"; this.dom.status.textContent = "---"; this.dom.targetLabel.textContent = "Select Monster"; return; } const mSave = State.variables.bestiary.speciesSave[id]; this.dom.id.textContent = id; this.dom.targetLabel.textContent = id; this.dom.status.textContent = mSave ? mSave.status : "Locked"; } setStatus(status) { if (!this.currentId) return; console.log(`[Debug] Force set status: ${this.currentId} -> ${status}`); bestiaryManager.unlockSpecies(this.currentId, status); EventBus.emit("data:updated", { speciesId: this.currentId }); } doAction(key, val) { if (!this.currentId) return; console.log(`[Debug] Action: ${key} += ${val}`); EventBus.emit("action:performed", { id: this.currentId, action: key, value: val }); } toggleFlag(flag) { if (!this.currentId) return; const mSave = State.variables.bestiary.speciesSave[this.currentId]; if(!mSave) return; const current = mSave.flags[flag] || false; bestiaryManager.setFlag(this.currentId, flag, !current); console.log(`[Debug] Flag '${flag}' set to ${!current}`); EventBus.emit("data:updated", { speciesId: this.currentId }); } resetAll() { if(confirm("Reset all bestiary data?")) { bestiaryManager.debugReset(); location.reload(); } } unlockAll() { Object.keys(setup.speciesDB).forEach(id => { bestiaryManager.unlockSpecies(id, 'sighted'); }); Msg.flash("All species sighted!"); location.reload(); } } new DebugPanel(); <</script>>
<style> .heartshow-container { position: relative; width: 20rem; height: 20rem; margin: auto; overflow: hidden; } .heartshow-particle { position: absolute; font-size: 1rem; will-change: transform, opacity; transition: transform 0.8s ease-out, opacity 0.5s ease-out; } </style> <div id="heartshowDemo"></div> <<done>> <<script>> function createHeartshowEffect(containerId, options = {}) { const config = { width: 20, height: 20, colors: ['#ff4d6d','#ff8a00','#e52e71','#ff0055'], particleCount: 15, burstInterval: 1200, maxDistance: 25, ...options }; const container = document.getElementById(containerId); container.innerHTML = ` <div class="heartshow-container" id="${containerId}_box"></div> `; const box = document.getElementById(`${containerId}_box`); box.style.width = `${config.width}rem`; box.style.height = `${config.height}rem`; let heartshowInterval; let activeHearts = 0; function heartshowCreate() { const h = document.createElement('div'); h.className = 'heartshow-particle'; h.innerHTML = '♥'; h.style.color = config.colors[Math.floor(Math.random()*config.colors.length)]; h.style.fontSize = `${0.9375 + Math.random()*0.9375}rem`; const halfSize = parseFloat(h.style.fontSize) / 2; h.style.left = `calc(50% - ${halfSize}rem)`; h.style.top = `calc(50% - ${halfSize}rem)`; h.style.opacity = '0'; box.appendChild(h); activeHearts++; setTimeout(() => { const ang = Math.random()*Math.PI*2; const dist = config.maxDistance * (0.5 + Math.random()*1.5); const tx = 50 + Math.cos(ang)*dist*2; const ty = 50 + Math.sin(ang)*dist*2; h.style.left = `calc(${tx}% - ${halfSize}rem)`; h.style.top = `calc(${ty}% - ${halfSize}rem)`; h.style.opacity = '1'; setTimeout(() => { h.style.opacity = '0'; setTimeout(() => { h.remove(); activeHearts--; if(activeHearts === 0 && !heartshowInterval) { heartshowStart(); } }, 800); }, 1000); }, 0); } function heartshowBurst() { for(let i=0; i<config.particleCount; i++) { setTimeout(heartshowCreate, i*30); } } function heartshowStart() { heartshowBurst(); heartshowInterval = setInterval(heartshowBurst, config.burstInterval); } function heartshowStop() { clearInterval(heartshowInterval); heartshowInterval = null; } box.addEventListener('click', () => { if(heartshowInterval) { heartshowStop(); } else { heartshowStart(); } }); return { start: heartshowStart, stop: heartshowStop, destroy: () => { heartshowStop(); box.innerHTML = ''; } }; } const heartshow = createHeartshowEffect('heartshowDemo', { width: 25, height: 25, colors: ['#ff4d6d', '#ff0055', '#00ffaa'], particleCount: 20, maxDistance: 30 }); heartshow.start(); <</script>> <</done>>
<<set _img2 = "https://image.zhangxinxu.com/image/blog/201905/landscape-s.jpg" >> <svg width="300" height="400" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> <defs> <filter id="snowEffect2"> <feImage href="https://image.zhangxinxu.com/image/blog/201905/snow.jpg" width="100%" height="100%" result="snowSource2" /> <feBlend in="SourceGraphic" in2="snowSource2" mode="screen" /> </filter> </defs> <image href= "https://image.zhangxinxu.com/image/blog/201905/landscape-s.jpg" width="100%" height="100%" style="filter: url(#snowEffect2);" /> </svg>
<h2>图像调色示例</h2> <canvas id="couplecanvas"></canvas> <<script>> $(document).ready(function() { const targetHSL1 = [50, 0, 0]; const targetHSL2 = [90, 0, 0]; const imageUrl = 'images/ui/logo.png'; ColorAdjuster.adjustImageColors(imageUrl, targetHSL1, targetHSL2); }); <</script>>
<<button "增加100猫娘进难民营">> <<run PopSystem.batchAdd("feline", 100, "settlement");>> <</button>> <<button "增加100人类进男爵领">> <<run PopSystem.batchAdd("human", 100, "barony");>> <</button>> <<button "增加100allureborn进男爵领">> <<run PopSystem.batchAdd("allureborn", 100, "barony");>> <</button>> <<button "自动安置1000猫娘">> <<run const result = PopSystem.autoSettle("feline", 1000); State.variables.lastLoot = result; console.log(State.variables.lastLoot); >> <</button>> <<button "跳转测试">><<run Engine.play('Settlement Management');>><</button>>
<<cardlink>> <div class="dispatch-block-1"> <!-- 这里是显示层,可以立即读取当前变量 --> <<= 66666>> <div class="dispatch-matchCount"><<= 55555>></div> <<set _cds = 7896>> </div> <<onclick>> <!-- 这里是逻辑层,点击时才运行 --> <<set _cds += 1 >> <<run console.log("点击成功,变量值为: " + _cds)>> <</cardlink>>
<<CardPop $chars.dataMap.get("you") $chars.dataMap.get("catmaid")>>
<<pickup $storage "Basic_Infuser_Core" 1>> <<run EventBus.emit("tech:requestUnlock", "infuser1");>>
<<run console.log(setup.geneUtils.getBioBaseValue("allureborn_succubus", "dimensions", "pussy", "depth"))>> <<run console.log(setup.geneUtils.getBioBaseValue("allureborn_succubus", "bodyScale"))>>
<style> .debugpages { gap: 2rem; display: flex; flex-wrap: wrap; align-content: flex-start; } </style> <<set _codex = Story.filter(function (c) {return c.name.startsWith("0") || c.name.startsWith("1");})>> <div class="debugpages"> <<for _tmp range _codex>> <div class="debugpage">[[_tmp.name]]</div> <</for>> </div>
<<set $player = { name: "艾琳娜", lv: 45, skills: ["暗影步", "血之契约"] }>> $player.lv <div class="character-card" @data-character=$player.lv @data-type=$player.name @id=$player.lv onclick="showCharacterInfo(this)"> <<= $player.name>> - 等级 <<= $player.lv>> </div> <<script>> window.showCharacterInfo =function(element) { const charData = element.dataset.character; Msg.flash(charData) } <</script>>
<<set _condition = () => false>> <<set setup.entrytitle = { "en": `Appearance` } >> <<set setup.entrycontent = { "en": `<h2 class="entry-header"><<= setup.entrytitle[$lang]>></h2> <table class="codex-table"> <thead> <tr> <th>Score Range</th> <th>Tier</th> <th>Term</th> <th>Description</th> </tr> </thead> <tbody> <tr> <td>181-200</td> <td>Mythic 1</td> <td>???</td> <td></td> </tr> <tr> <td>161-180</td> <td>Mythic 2</td> <td>???</td> <td></td> </tr> <tr> <td>141-160</td> <td>Mythic 3</td> <td>???</td> <td></td> </tr> <tr> <td>121-140</td> <td>Mythic 4</td> <td>???</td> <td></td> </tr> <tr> <td>101-120</td> <td>Mythic 5</td> <td>Otherworldly Grace</td> <td>Appearance beyond mortal aesthetics, with planar elegance and ineffable charm, both familiar and alien.</td> </tr> <tr> <td>91-100</td> <td>Mortal 1</td> <td>Breathtaking Beauty</td> <td>Stunningly captivating appearance, rare in the mortal realm, drawing all eyes instantly.</td> </tr> <tr> <td>81-90</td> <td>Mortal 2</td> <td>Striking Beauty</td> <td>Exceedingly beautiful with strong visual impact and refined presence, deeply memorable.</td> </tr> <tr> <td>71-80</td> <td>Mortal 3</td> <td>Radiant Charm</td> <td>Beauty and charisma combined, radiating an enchanting aura, warm and captivating.</td> </tr> <tr> <td>61-70</td> <td>Mortal 4</td> <td>Captivating Allure</td> <td>Attractive with distinctive charm, easily enthralling others.</td> </tr> <tr> <td>51-60</td> <td>Mortal 5</td> <td>Attractive</td> <td>Pleasing appearance, aligning with common aesthetics, harmonious but not striking.</td> </tr> <tr> <td>41-50</td> <td>Mortal 6</td> <td>Pleasant</td> <td>Ordinary but inoffensive appearance, mild and unremarkable.</td> </tr> <tr> <td>31-40</td> <td>Mortal 7</td> <td>Unremarkable</td> <td>Utterly average appearance, blending into the crowd, lacking allure.</td> </tr> <tr> <td>21-30</td> <td>Mortal 8</td> <td>Plain</td> <td>Below-average appearance, lacking beauty, easily overlooked.</td> </tr> <tr> <td>11-20</td> <td>Mortal 9</td> <td>Unappealing</td> <td>Off-putting appearance, with discordant features or flaws, hard to like.</td> </tr> <tr> <td>0-10</td> <td>Mortal 10</td> <td>Repulsive</td> <td>Repellent or terrifying appearance, possibly with grotesque traits.</td> </tr> </tbody> </table> <div> <p><strong>Appearance Mechanism:</strong> The Appearance value affects the increase in happiness during interactions and the likelihood of developing hobbies. It is determined by a base value and Charisma, and can be inherited.</p> </div> ` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Core Stats` } >> <<set setup.entrycontent = { "en": `<div class="codex-container"> <div class="codex-header"> <i class="fas fa-book-reader"></i> <<= setup.entrytitle[$lang]>> </div> <div style="text-align:center; color:#777; font-style:italic; margin-bottom:10px;"> Core Attributes governing the soul's potential. </div> <div class="page-card-list card-list p-2 bg-body"> <div class="page-card card-slot"> <div class="slab-header"> <div class="slab-icon"><i class="fas fa-comments"></i></div> <div class="slab-title-group"> <span class="slab-abbr">CHA</span> <span class="slab-name"><<= setup.langbank.cha[$lang]>></span> </div> </div> <div class="slab-desc"><<= setup.langbank.chadesc[$lang]>></div> <div class="slab-effects"> <div class="effect-row"> <i class="fas fa-circle-notch effect-icon eff-ritual"></i> <span><span class="eff-ritual">Ritual:</span> Amplifies EXP gained.</span> </div> <div class="effect-row eff-locked"> <i class="fas fa-lock effect-icon"></i> <span><span class="eff-combat">Combat:</span> <em>Unknown mystery...</em></span> </div> </div> </div> <div class="page-card card-slot"> <div class="slab-header"> <div class="slab-icon"><i class="fas fa-wind"></i></div> <div class="slab-title-group"> <span class="slab-abbr">DEX</span> <span class="slab-name"><<= setup.langbank.dex[$lang]>></span> </div> </div> <div class="slab-desc"><<= setup.langbank.dexdesc[$lang]>></div> <div class="slab-effects"> <div class="effect-row"> <i class="fas fa-circle-notch effect-icon eff-ritual"></i> <span><span class="eff-ritual">Ritual:</span> Reduces Energy cost.</span> </div> <div class="effect-row eff-locked"> <i class="fas fa-lock effect-icon"></i> <span><span class="eff-combat">Combat:</span> <em>Unknown mystery...</em></span> </div> </div> </div> <div class="page-card card-slot"> <div class="slab-header"> <div class="slab-icon"><i class="fas fa-seedling"></i></div> <div class="slab-title-group"> <span class="slab-abbr">FER</span> <span class="slab-name"><<= setup.langbank.fer[$lang]>></span> </div> </div> <div class="slab-desc"><<= setup.langbank.ferdesc[$lang]>></div> <div class="slab-effects"> <div class="effect-row"> <i class="fas fa-circle-notch effect-icon eff-ritual"></i> <span><span class="eff-ritual">Ritual:</span> Boosts success rates & fate.</span> </div> <div class="effect-row eff-locked"> <i class="fas fa-lock effect-icon"></i> <span><span class="eff-combat">Combat:</span> <em>Unknown mystery...</em></span> </div> </div> </div> <div class="page-card card-slot"> <div class="slab-header"> <div class="slab-icon"><i class="fas fa-heartbeat"></i></div> <div class="slab-title-group"> <span class="slab-abbr">STA</span> <span class="slab-name"><<= setup.langbank.sta[$lang]>></span> </div> </div> <div class="slab-desc"><<= setup.langbank.stadesc[$lang]>></div> <div class="slab-effects"> <div class="effect-row"> <i class="fas fa-circle-notch effect-icon eff-ritual"></i> <span><span class="eff-ritual">Ritual:</span> Max Daily Actions limit.</span> </div> <div class="effect-row eff-locked"> <i class="fas fa-lock effect-icon"></i> <span><span class="eff-combat">Combat:</span> <em>Unknown mystery...</em></span> </div> </div> </div> <div class="page-card card-slot"> <div class="slab-header"> <div class="slab-icon"><i class="fas fa-fist-raised"></i></div> <div class="slab-title-group"> <span class="slab-abbr">STR</span> <span class="slab-name"><<= setup.langbank.str[$lang]>></span> </div> </div> <div class="slab-desc"><<= setup.langbank.strdesc[$lang]>></div> <div class="slab-effects"> <div class="effect-row"> <i class="fas fa-circle-notch effect-icon eff-ritual"></i> <span><span class="eff-ritual">Ritual:</span> Enhances EXP potency.</span> </div> <div class="effect-row eff-locked"> <i class="fas fa-lock effect-icon"></i> <span><span class="eff-combat">Combat:</span> <em>Unknown mystery...</em></span> </div> </div> </div> <div class="page-card card-slot"> <div class="slab-header"> <div class="slab-icon"><i class="fas fa-eye"></i></div> <div class="slab-title-group"> <span class="slab-abbr">WIL</span> <span class="slab-name"><<= setup.langbank.wil[$lang]>></span> </div> </div> <div class="slab-desc"><<= setup.langbank.wildesc[$lang]>></div> <div class="slab-effects"> <div class="effect-row"> <i class="fas fa-circle-notch effect-icon eff-ritual"></i> <span><span class="eff-ritual">Ritual:</span> Summon quality & quantity.</span> </div> <div class="effect-row eff-locked"> <i class="fas fa-lock effect-icon"></i> <span><span class="eff-combat">Combat:</span> <em>Unknown mystery...</em></span> </div> </div> </div> </div> <div class="codex-section theme-abyss" style="margin-top: 30px;"> <div class="section-title txt-abyss"> <i class="fas fa-infinity"></i> The Law of Growth </div> <div class="formula-display"> ( <span class="var-base">Base</span> + <span class="var-base">Add</span> ) × ( 1 + <span class="var-mult">Rate</span> ) × Coefficient + <span class="var-real">RealValue</span> </div> <div style="font-size: 0.9em; color: #888;"> * Growth rate and limitations are dictated by one's natural <span class="txt-abyss">Talent</span>. </div> </div> </div>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Main Traits` } >> <<set setup.entrycontent = { "en": `<div class="codex-container"> <div class="codex-header"> <i class="fas fa-dna"></i> Main Traits Codex </div> <div style="text-align:center; color:#777; font-style:italic; margin-bottom:15px;"> Innate abilities and bloodline gifts manifested in the physical form. </div> <div class="page-card-list card-list p-2 bg-body"> <<for _key, _trait range setup.traitdata_talent>> <<TraitCard _key>> <</for>> </div> </div>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Genes Base` } >> <<set setup.entrycontent = { "en": `<div class="codex-container"> <div class="codex-header"> <i class="fas fa-microchip"></i> Mechanics of Evolution </div> <div style="text-align:center; color:#777; font-style:italic; margin-bottom:25px;"> From the mastery of flesh to the manifestation of power. </div> <div class="evo-container"> <!-- ================= PHASE I: GENETIC ORIGIN ================= --> <div> <div class="phase-header" style="border-color: #8a1c1c;"> <span class="phase-title" style="color: #d9534f;">I. The Origin</span> <span class="phase-tag tag-internal">INTERNAL · HERITABLE</span> </div> <div class="page-card-list card-list p-2 bg-body"> <!-- 1. Bodypart Mastery --> <div class="page-card card-slot"> <div class="slab-header"> <div class="slab-icon"><i class="fas fa-dumbbell"></i></div> <div class="slab-title-group"> <span class="slab-abbr">MASTERY</span> <span class="slab-name">Bodypart Experience</span> </div> </div> <div class="slab-desc"> Accumulated memory of the flesh. When a specific body part reaches mastery limits (100%), it triggers a permanent genetic evolution. </div> <div> <div style="display:flex; justify-content:space-between; font-size:0.75em; color:#aaa;"> <span>Evolution Progress</span> <span>60%</span> </div> <div class="progress-mini-container"> <div class="progress-mini-fill" style="width: 60%;"></div> </div> </div> <div class="source-icons"> <span class="source-item" title="Sex Acts"><i class="fas fa-heart"></i> Intimacy</span> <span class="source-item" title="Training"><i class="fas fa-key"></i> Training</span> </div> </div> <!-- 2. Genetic Potential --> <div class="page-card card-slot"> <div class="slab-header"> <div class="slab-icon"><i class="fas fa-dna"></i></div> <div class="slab-title-group"> <span class="slab-abbr">GENES</span> <span class="slab-name">Innate Potential</span> </div> </div> <div class="slab-desc"> The biological limit of the soul. <br> <em>Formula: Base + (Innate Mod + Mastery Mod)</em><br> These values can be <strong>inherited</strong> by offspring, potentially mutating into stronger forms. </div> <div class="slab-effects"> <span class="txt-highlight">Example:</span> STR +9, STA +3, DEX -2 </div> </div> <!-- 3. Synthesis (Tier) --> <div class="page-card card-slot" style="border-top-color: #d9534f;"> <div class="slab-header"> <div class="slab-icon"><i class="fas fa-layer-group"></i></div> <div class="slab-title-group"> <span class="slab-abbr">TIER</span> <span class="slab-name">Total Synthesis</span> </div> </div> <div class="slab-desc"> The aggregate score of all genetic potentials. This determines the entity's <strong>Growth Tier</strong> (S, A, B...). </div> <div class="slab-effects"> <span class="txt-highlight">Effect:</span> Higher Tier = Massive EXP Multiplier (up to 800%). </div> </div> </div> </div> <!-- Flow Connector --> <div class="flow-connector"> <i class="fas fa-chevron-down"></i> </div> <!-- ================= PHASE II: MANIFESTATION ================= --> <div> <div class="phase-header" style="border-color: #1c3b8a;"> <span class="phase-title" style="color: #5bc0de;">II. The Manifestation</span> <span class="phase-tag tag-external">EXTERNAL · TEMPORARY</span> </div> <div class="page-card-list card-list p-2 bg-body"> <!-- 4. Stat Experience --> <div class="page-card card-slot"> <div class="slab-header"> <div class="slab-icon"><i class="fas fa-flask"></i></div> <div class="slab-title-group"> <span class="slab-abbr">EXP</span> <span class="slab-name">Stat Experience</span> </div> </div> <div class="slab-desc"> Daily growth gained from actions. The rate of gain is strictly controlled by the <strong>Tier</strong>. </div> <div> <div style="display:flex; justify-content:space-between; font-size:0.75em; color:#aaa;"> <span>To Next Level</span> <span>45%</span> </div> <div class="progress-mini-container"> <div class="progress-mini-fill" style="width: 45%; background-color: #5bc0de;"></div> </div> </div> <div class="source-icons"> <span class="source-item"><i class="fas fa-fist-raised"></i> Battle</span> <span class="source-item"><i class="fas fa-hammer"></i> Labor</span> </div> </div> <!-- 5. Stat Value (Final) --> <div class="page-card card-slot" style="border-top-color: #5bc0de;"> <div class="slab-header"> <div class="slab-icon"><i class="fas fa-chart-bar"></i></div> <div class="slab-title-group"> <span class="slab-abbr">STATS</span> <span class="slab-name">True Power</span> </div> </div> <div class="slab-desc"> The visible power value (STR: 15). This is the result, not the cause. It resets upon rebirth/death, unlike Genes. </div> <div class="slab-effects"> <div class="effect-row"><i class="fas fa-check effect-icon eff-combat"></i> Determines Mission Success</div> <div class="effect-row"><i class="fas fa-check effect-icon eff-ritual"></i> Determines Resource Yield</div> </div> </div> </div> </div> <!-- ================= SUMMARY FOOTER ================= --> <div class="summary-list"> <div style="margin-bottom:10px; text-transform:uppercase;"> <i class="fas fa-info-circle"></i> Key Takeaways </div> <ul class="codex-list"> <li> A freshly summoned high-tier monster will have <span class="txt-highlight">High Genetic Potential</span> (S-Tier) but <span class="txt-highlight">Low Base Stats</span> (Level 1). </li> <li> However, thanks to her <span class="txt-highlight">S-Tier</span> genes, she will gain EXP at <strong>800% speed</strong>, quickly surpassing common monsters. </li> <li> <strong>Genes are eternal; Stats are transient.</strong> Focus on breeding better genes to raise the ceiling of your army. </li> </ul> </div> </div> </div>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Genes Desc` } >> <<set setup.entrycontent = { "en": `<<include "Gene_Help">>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Trait Bloodline` } >> <<set setup.entrycontent = { "en": `<div class="codex-container"> <div class="codex-header"> <i class="fas fa-book-dead"></i> The Bloodline Codex </div> <div class="codex-section theme-abyss"> <div class="section-title txt-abyss"> <i class="fas fa-balance-scale"></i> Laws of Inheritance </div> <div>Power is not guaranteed; it is inherited through the strength of blood.</div> <ul class="codex-list"> <li><span class="txt-light">Trait Mastery:</span> The higher the Trait Level, the greater the chance it will pass to the offspring.</li> <li><span class="txt-light">Rarity Penalty:</span> Legendary and Rare traits are volatile and harder to inherit than common ones.</li> <li><span class="txt-light">Unique Souls:</span> Traits marked as <span class="txt-dim">"Non-hereditary"</span> are bound to the individual and die with them.</li> </ul> </div> <div class="codex-section theme-blood"> <div class="section-title txt-blood"> <i class="fas fa-heartbeat"></i> Blood Resonance </div> <div>When <span class="txt-light">both parents</span> possess the same trait, the bloodline strengthens.</div> <ul class="codex-list"> <li>The chance of inheritance is <span class="txt-light">significantly increased</span>.</li> <li>The offspring will inherit the trait at a <span class="txt-light">Higher Level</span> than the parents (up to the cap).</li> </ul> <div class="txt-dim" style="margin-top:5px;">"Pure bloodlines breed power."</div> </div> <div class="codex-section theme-gold"> <div class="section-title" style="color: #cbb06a;"> <i class="fas fa-flask"></i> Alchemy of Flesh </div> <div>Conflicting or complementary blood can trigger unpredictable reactions:</div> <ul class="codex-list"> <li> <span class="txt-light">Fusion:</span> Certain trait combinations will merge, consuming the old traits to create a <span class="txt-abyss">New, Superior Trait</span>. </li> <li> <span class="txt-light">Dominance:</span> Conflicting traits (e.g., Ice & Fire) cannot coexist. The <span class="txt-blood">Stronger Trait</span> (Higher Level) will devour the weaker one. </li> </ul> </div> <div class="codex-section theme-abyss"> <div class="section-title txt-abyss"> <i class="fas fa-dna"></i> Spontaneous Mutation </div> <div> Rarely, the blood may evolve on its own. A newborn may manifest a trait at a higher potential than its lineage should allow. </div> </div> <div style="text-align: center; font-size: 0.8em; color: #555; margin-top: 15px; letter-spacing: 1px;"> — VERITAS VOS LIBERABIT — </div> </div>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Aiming Modes` } >> <<set setup.entrycontent = { "en": `<h2 class="entry-header"><<= setup.entrytitle["en"]>></h2> <div style="line-height: 1.5;"> <h2 style="color: #8b0000; border-bottom: 2px solid #8b0000; padding-bottom: 5px;">🏹 Targeting Directives</h2> <h3 style="color: #6a4c32; margin-top: 15px;">Nearest (Swift Response)</h3> <p>Targets the closest foe. Best for picking off swift siege-breakers before they reach the walls. Keeps the vanguard thin and buys us time before the Morale phase turns dire.</p> <h3 style="color: #6a4c32; margin-top: 15px;">FocusBoss (Execution)</h3> <p>Locks onto the strongest enemy on the field. Concentrated fire takes down high-health champions and infernal commanders, neutralizing the greatest threats to our Fortitude before they can break our line.</p> <h3 style="color: #6a4c32; margin-top: 15px;">OneToOne (Suppression)</h3> <p>Distributes fire across the entire swarm. Rather than killing one, it weakens many, stalling the horde's momentum and easing the pressure on our walls. Essential against massive waves.</p> </div>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Defense Command` } >> <<set setup.entrycontent = { "en": `<h2 class="entry-header"><<= setup.entrytitle["en"]>></h2> <div style="line-height: 1.5;"> <h3 style="color: #8b0000;">⚔️ Commander [STR: Attack]</h3> <p>The Vanguard. Strength fuels your critical hits and combat power. Assign this role to crush through enemy ranks during charges and hold the ramparts with brutal force.</p> <h3 style="color: #8b0000; margin-top: 20px;">🛡️ Warden [STA: Health]</h3> <p>The Bulwark. Stamina extends your maximum health and supercharges repair skills. The Warden is the frontline shield—absorbing damage while keeping the city’s fortifications standing.</p> <h3 style="color: #8b0000; margin-top: 20px;">🎶 Bard [CHA: Regen]</h3> <p>The Spirit. Charisma accelerates health regeneration and boosts healing efficacy. Essential for sustaining the frontline through long sieges and keeping the Guard combat-ready.</p> <h3 style="color: #8b0000; margin-top: 20px;">🏹 Guardian [DEX: Armor]</h3> <p>The Protector. Dexterity mitigates incoming damage and boosts arrow tower fire rate. A dual-threat role that shields allies in melee while raining death from the towers.</p> <h3 style="color: #8b0000; margin-top: 20px;">🎯 Ranger [FER: Crit]</h3> <p>The Marksman. Fertility (Divine Luck) increases critical hit chance for both the Guard and your towers. Vital for dismantling high-priority targets and executing capture maneuvers.</p> <h3 style="color: #8b0000; margin-top: 20px;">🧠 Strategist [WIL: Charge]</h3> <p>The Mind. Willpower reduces skill cooldowns and hastens Morale recovery. The Strategist dictates the pace of the battle, ensuring your most powerful abilities are always ready when the horde breaks through.</p> </div>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Fortitude & Morale` } >> <<set setup.entrycontent = { "en": `<h2 class="entry-header"><<= setup.entrytitle["en"]>></h2> <div style="line-height: 1.5;"> <h3 style="color: #8b0000; border-bottom: 2px solid #8b0000; padding-bottom: 5px;">🏰 Fortitude (City Walls)</h3> <p>The collective integrity of our ramparts. This is the city's primary health pool. Every siege-breaker’s blow chips away at this stone and iron barrier.</p> <ul style="padding-left: 20px;"> <li><strong>Restoration:</strong> Use <em>Repair Fortifications</em> to mend breaches.</li> <li><strong>Breach:</strong> When Fortitude reaches zero, the gates collapse.</li> <li><strong>Consequence:</strong> The defensive line falls, forcing your troops into the desperate <em>Morale Phase</em>.</li> </ul> <h3 style="color: #6a4c32; margin-top: 25px;">⚔️ Morale (Final Stand)</h3> <p>The defiant spirit of your Guard. Once the walls fall, Morale becomes humanity's final bulwark against the horde.</p> <ul style="padding-left: 20px;"> <li><strong>Mechanic:</strong> Courage wanes under relentless damage. If it hits zero, all defenders perish—and the city falls.</li> <li><strong>Tactics:</strong> Strategists are essential here; their Willpower accelerates recovery and keeps the line from snapping.</li> <li><strong>Risk:</strong> In dire straits, troops may rally for a final, costly sacrifice to buy more time.</li> </ul> <p style="font-style: italic; font-size: 0.9em; margin-top: 10px;">Protect the walls at all costs. The Morale phase is not a tactic—it is a last resort.</p> </div>` }>>
<<set _condition = () => $times.total >= 4>> <<set setup.entrytitle = { "en": `Black Market` } >> <<set setup.entrycontent = { "en": `<div style="line-height: 1.5;"> <h2 style="color: #8b0000; border-bottom: 2px solid #8b0000; padding-bottom: 5px;">💰 Black Market</h2> <p>In a world choked by demons, a marketplace this brazen shouldn't exist. The rumors point to backers far beyond your darkest imaginings. Their wares can be a lifeline... if you have the stomach for their prices.</p> <h3 style="color: #6a4c32; margin-top: 20px;">Market Volatility</h3> <p>Prices shift like quicksand. Buying low and selling high takes deep pockets and a gambler's luck.</p> <h3 style="color: #6a4c32; margin-top: 20px;">The Cost of Trust</h3> <p>The base markup is a staggering 200%. If you want a better rate, don't look for goodwill—look for gold.</p> <ul style="padding-left: 20px;"> <li><strong>Volume:</strong> Consistent trading slowly lowers the price.</li> <li><strong>Influence:</strong> Special events can open doors—and discounts.</li> </ul> <p><em>Remember: "Loyalty" here is just another commodity.</em></p> <h3 style="color: #6a4c32; margin-top: 20px;">Weekly Finds</h3> <p>Stock refreshes every week. Keep an eye out for:</p> <ul style="padding-left: 20px;"> <li>Rare resources.</li> <li>Legendary-tier artifacts.</li> </ul> <p><em>Bring a full purse. The best deals don't linger for the hesitant.</em></p> </div>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Chambers` } >> <<set setup.entrycontent = { "en": `<div style="line-height: 1.5;"> <h2 style="color: #8b0000; border-bottom: 2px solid #8b0000; padding-bottom: 5px;">⛓️ Repurposed Chambers</h2> <p>I can’t stop thinking about these chambers. Deep beneath the Temple, a labyrinth carved into the rock. It was a prison. A cold, cruel cage for Beastkin slaves, built by hands that saw them as less than human. The walls? They still whisper that shame—scratches where chains hung, corners stained with despair. It twists my gut just to walk through them. How could anyone sink so much effort into building a cage?</p> <p>It’s staggering in size. Countless rooms branching off winding halls, each built with intent. Rough stonework, deliberate planning. Not some hasty dungeon. This took pride. That’s what haunts me: the care put into something so wrong. I look at these walls and imagine the spirits broken here, the untold stories trapped in the dark. Why keep this place secret? Why let it rot instead of tearing it down? I don't get it.</p> <h3 style="color: #6a4c32; margin-top: 25px;">A Second Chance</h3> <p>But that’s the past. I’ve gutted it. The cages? Gone. I had them ripped out myself. Every Beastkin here is free now. No locks. No chains. Just open doors. The problem is, they’ve got nowhere else to go. They’re still living in the rooms that once trapped them. It feels wrong—like a failure. But I swear, it’s temporary.</p> <p>These aren't prison cells anymore. They’re a start. With a little elbow grease, this place can house hundreds, maybe thousands. Not just Beastkin, but anyone who joins our cause. Bedrooms, workshops, markets... the foundation is solid. It just needs a heart.</p> <p>I’m calling this the <em>Repurposed Chambers</em>. A second chance—for the Beastkin, for me, for this whole broken fief. As Lord, I won't let this stay a grim relic. I want to hear families laughing here. Lights in the windows. It’s a massive task, but I have to try. The world is falling apart—demons at the gates, taxes bleeding us dry—but these chambers? This is my foothold. A place to stand and fight.</p> </div>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Main Hall` } >> <<set setup.entrycontent = { "en": `<div style="line-height: 1.5;"> <h2 style="color: #8b0000; border-bottom: 2px solid #8b0000; padding-bottom: 5px;">🏛️ Lost Temple</h2> <p>This temple. It’s breathtaking. Carved deep into the mountain’s heart—a masterpiece of dark gothic grandeur. Towering spires, intricate stone carvings, chambers whispering of devotion long lost. Coming from the modern world, its haunting beauty floors me. Craftsmanship like this? Must’ve taken lifetimes.</p> <p>Once, this was a thriving hub—halls and rooms built for a community. Now? Forgotten. Crumbling in silence. Why? It baffles me. My predecessors treated this marvel like a secret hideout, never sharing its wonders, letting time gnaw its glory away. Afraid of its power? Or just indifferent? It’s a puzzle I can’t shake.</p> <h3 style="color: #6a4c32; margin-top: 25px;">Main Hall & Rift</h3> <p>At the entrance: the Main Hall. A ritual platform lies at its center, scarred by a violent explosion that shattered the jagged stone. Yet, in the ruin, something incredible: <strong>a Planar Rift</strong>. It pulses with a heartbeat from another world.</p> <p>The Goddess claims she formed this rift by seizing a demon's portal, draining her divine power. Looking at her now—diminutive, no bigger than a ceramic figurine—I’m skeptical. Still, I can’t deny the energy. This rift is real. A key to summoning interdimensional allies—a power both thrilling and terrifying.</p> <h3 style="color: #6a4c32; margin-top: 25px;">Reclaiming the Future</h3> <p>Abandoned rooms, big and small, scattered everywhere. A gift in these desperate times. Dusty, worn, but they’ll serve as my stronghold. Storage, workshops, homes for my people... I see the potential. Step by step, I’ll breathe life back into these halls. Oblivion doesn't deserve this place.</p> </div>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Planar Rift` } >> <<set setup.entrycontent = { "en": `<div style="line-height: 1.5;"> <h2 style="color: #8b0000; border-bottom: 2px solid #8b0000; padding-bottom: 5px;">🔮 Planar Rift Guide</h2> <div style="text-align: center; margin: 15px 0;">[img[setup.ImagePath + "ui/help/summonhelpen.webp"]]</div> <div style="background: rgba(139, 0, 0, 0.05); border: 1px solid #8b0000; padding: 15px; border-radius: 8px; margin-bottom: 20px;"> <h3 style="margin-top: 0; color: #8b0000;">💡 Strategist's Insight</h3> <p style="font-style: italic; margin-bottom: 0;">Focus on cultivating high-level monsters via alchemy potions. A high-level monster acts as a catalyst, accelerating the growth of its partners. Remember: the primary goal is maximizing quest bounties and high-quality resource yields, not just breeding volume.</p> </div> <h3 style="color: #6a4c32; border-bottom: 1px solid #8c6d48;">Ritual Mechanics</h3> <ul style="padding-left: 20px;"> <li><strong>Soul Resonance:</strong> Summoning draws power from the combined resonance of the pair. Pairing high-resonance monsters with rare specimens yields better results.</li> <li><strong>Life Seeds (Pregnancy):</strong> Successful conception grants a "Life Seed," determining the summoned entity's attributes. <em>Warning: Exercise caution during events like the Blood Moon—unknown dangers may arise.</em></li> </ul> <h3 style="color: #6a4c32; border-bottom: 1px solid #8c6d48;">Attributes & Growth</h3> <p><strong>Core Stats:</strong> STA (Stamina), STR (Strength), CHA (Charisma), WIL (Willpower), DEX (Dexterity), FER (Fertility).</p> <ul style="padding-left: 20px;"> <li><strong>Talent Tiers:</strong> Each stat has a tier governing its growth rate and cap. Breakthroughs enhance these tiers and improve summoning outcomes.</li> <li><strong>AP (Action Points):</strong> Derived from STA. Replenish via rest or consumption.</li> <li><strong>EXP:</strong> Gained through training and ritual resonance. Values depend on the synergy between both parties.</li> </ul> <h3 style="color: #6a4c32; border-bottom: 1px solid #8c6d48;">Harvest & Yield</h3> <ul style="padding-left: 20px;"> <li><strong>Yield Calculation:</strong> Quantity is determined by AP; Quality scales with the monster's Level and WIL attribute.</li> <li><strong>Efficiency:</strong> Harvesting drains all remaining AP. High WIL also boosts resource refinement.</li> <li><strong>Optimization:</strong> Use Dexterity (DEX) potions to reduce summoning costs and increase daily ritual frequency.</li> </ul> </div>` }>>
<<set _condition = () => $times.total >= 2>> <<set setup.entrytitle = { "en": `Slaver Caravan` } >> <<set setup.entrycontent = { "en": `<div style="line-height: 1.5;"> <h2 style="color: #8b0000; border-bottom: 2px solid #8b0000; padding-bottom: 5px;">⛓️ Slaver Caravan</h2> <p> These wretched caravans are a stain on this world—peddling misery for gold. Run by mercenaries who trap Beastkin through poison, lies, and kidnapping. Every fiber of my being loathes them. Yet, as much as this trade sickens me, these thralls are the fastest way to bolster our fief’s strength. <br><br> <i>Mark my words: one day, I will make them pay for every soul they’ve stolen.</i> </p> <h3 style="color: #6a4c32; margin-top: 20px;">Available Species</h3> <p>The war drags on, dumping more "inventory" into these caravans. Some rare species won't show up unless you've cleared the right path in the realm first.</p> <h3 style="color: #6a4c32; margin-top: 20px;">Market Prices</h3> <p>Empire laws have flooded the market with "cheap labor," keeping prices at rock-bottom for now. Capitalize on their greed while the supply lasts.</p> <h3 style="color: #6a4c32; margin-top: 20px;">Inspecting Traits</h3> <p>Don't just buy muscle. Rare traits hold the real power. Inspect every thrall carefully—the right talent is worth every single coin.</p> </div>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Summon Probability` } >> <<set setup.entrycontent = { "en": `<div> <h2 style="color: #8b0000; text-align: center; margin-top: 0; text-shadow: none;">🔮 Summoning Ritual Odds</h2> <p style="font-size: 0.95em; line-height: 1.5;">The success of your ritual is determined by the combined resonance of both parents. The outcomes are categorized into five tiers:</p> <div style="margin: 15px 0; border-left: 3px solid #8c6d48; padding-left: 10px;"> <div style="margin-bottom: 5px;"><strong>Class 0-1:</strong> Planar Junk</div> <div style="margin-bottom: 5px;"><strong>Class 2:</strong> Raw Resources</div> <div style="margin-bottom: 5px;"><strong>Class 3:</strong> Alchemy Potions</div> <div style="margin-bottom: 5px;"><strong>Class 4:</strong> New Monster</div> </div> <h3 style="color: #6a4c32; border-bottom: 1px solid #8c6d48; padding-bottom: 5px; margin-top: 20px;">📜 Mechanics & Factors</h3> <ul style="padding-left: 20px; line-height: 1.6; font-size: 0.9em; list-style-type: square;"> <li><strong>Cumulative Resonance:</strong> Total odds are the sum of both parents' contributions. You can pair a high-chance parent with a rare one to balance the ritual.</li> <li><strong>Modifier Stack:</strong> Feed, Traits, Buildings, Hobbies, and Equipment provide permanent boosts. These bonuses <em>accumulate</em> and persist even if you swap partners.</li> <li><strong>Constraint Logic:</strong> If conditions (e.g., F&F, M&M) are not met, the ritual defaults to items/resources.</li> <li><strong>Ritual Purity:</strong> Pregnant monsters cannot participate to prevent genetic contamination.</li> </ul> </div>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Protagonist` } >> <<set setup.entrycontent = { "en": `<h2 class="entry-header">Me, the So-Called Savior</h2> Okay, let’s get this straight—I’m no hero. Picture me: blonde hair, office buzzcut, now crammed into black Armor that’s way too noble for a guy who once panicked over a jammed copier. I was your average office drone, slogging through emails till midnight. One night, stumbling home, I saw a blood moon—creepy, cinematic, like a horror flick’s warning. I went to snap a pic, and zap—lightning fries me. Next thing I know, I’m sprawled in a Lost Temple, carved into some mountain like a fantasy movie set. Welcome to Hawkridge Barony, they say. I’m Baron Wilderen, Lord of this dirt patch. Me. The guy who forgot his lines in a school play. The old lord? Dead, heroically or not, fighting for the fief. Now it’s my turn, except nobody sent the memo about demons ripping the world apart. Yeah, demons—big, ugly ones, flooding through Portals like it’s an apocalypse sale. My grand inheritance? A ruined territory, starving peasants, and a target on my back. I thought nobles got castles and feasts, not "nightmare mode" survival. To top it off, a ceramic angel—yes, a talking statue—shows up, claiming it yanked me here to save the world. Save it? I can barely save a Word doc without crashing. Here’s the kicker: I’m not buying the whole "chosen one" shtick. I’ve binged enough TV dramas to smell a plot twist. That angel’s hiding something, and so is everyone else. I catch weird looks in the temple—Selena’s too-quick smiles, Raymond’s dodged questions, little clues that don’t add up. This world’s got secrets, and I’m the odd man out. Friend or foe, I don’t know what’s waiting, so I’m playing dumb—faked amnesia, classic move. "Oh, my head hurts, tell me who I am." Works like a charm, but I’m watching, waiting for the truth to slip. Demons want me dead, human "allies" are probably scheming, and starvation’s knocking. My big plan? Team up with monster girls—because, sure, that’s normal—and lean on folks like Garvin and Erin to keep this mess from collapsing. Modern me wants to scream, "I’m not cut out for this!" but whining won’t stop the end of the world. So, I’ll grumble, fake some lordly confidence, and dig for answers. Why me? Why monster girls? Is this a goddess’s game or something worse? I’ll find out, even if it’s just to prove I’m not that guy who flunks at saving humanity. <<cimg $chars.dataMap.get("you").img>>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Head Maid` } >> <<set setup.entrycontent = { "en": `<h2 class="entry-header"><<= $chars.dataMap.get("headmaid").name>>, My Head Maid</h2> I woke up in the Lost Temple, head pounding, and there she was—Selena, my head maid. First thing I saw: her gentle hands wiping my forehead with a cloak, her voice soft but sure. "The light’s still there... The Goddess chose you. I’m your head maid, Selena." Those words hit me hard. Me, chosen? I’m just a guy who got zapped here, but her faith—it’s unshakable. It’s humbling. Selena’s a paradox: tough as steel, tender as a whisper. Her gold hair glows like sunlight, and her blue eyes hold a kindness that cuts through this world’s gloom. She’s beautiful, sure, but it’s her heart that floors me. Word is, during our escape, she defied the butler to save stragglers—dozens owe her their lives. Compassion like that? It’s rare, and it makes me want to be better. As Lord, I rely on her strength. She runs the temple like it’s her own, firm but never cold. I catch her smiling at the freed Beastkin, and I think: maybe she sees hope where I see chaos. Selena’s not just a maid—she’s a lifeline. I’m lucky she believes in me. <<cimg $chars.dataMap.get("headmaid").img>>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Butler` } >> <<set setup.entrycontent = { "en": `<h2 class="entry-header"><<= $chars.dataMap.get("butler").name>>, My Butler</h2> I met Raymond in the Lost Temple’s flickering torchlight, and let me tell you—he’s something else. At 50, he’s the picture of an English butler, down to his emerald velvet tailcoat, not a crease out of place. Gray hair, stern face, but there’s a warmth in his eyes that sneaks through, like he’s sizing you up and rooting for you at the same time. Even after demons destroyed our town, the man’s grace doesn’t falter. It’s unreal—I’d be a mess, but Raymond? Steady as stone. He’s been with my fief’s family forever, they say, serving the old Lord through who-knows-what. Reliable doesn’t cover it; he’s a rock. When he speaks, it’s all poise, every word measured, yet kind. I caught him organizing supplies with a nod and a smile—chaos doesn’t faze him. There’s a story behind those eyes, especially about the old lord. I need to ask him, soon. For now, as Lord, I’m just glad Raymond’s here to keep me from screwing this up. <<cimg $chars.dataMap.get("butler").img>>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Captain` } >> <<set setup.entrycontent = { "en": `<h2 class="entry-header"><<= $chars.dataMap.get("captain").name>>, My Guard Captain</h2> Garvin’s a walking fortress. I met him in the Lost Temple, clad in Armor so battered it’s more scratches than steel—marks of battles I can’t even imagine. He’s the guard captain, and he’s as straight-talking as they come: no fluff, just truth. Reliable? That’s an understatement. He never takes off his helmet—word is, a bad scar hides underneath. I respect his choice to keep it private; some wounds don’t need sharing. During our escape, demons—elite ones—ambushed us. Garvin faced them head-on, buying our survival with his blood. He’s alive, but barely. Injuries sapped his strength, yet he still stands watch at the valley’s defenses, day and night, keeping enemies out. It’s humbling, but it worries me. As Lord, I’ve got to train strong soldiers and build solid fortifications fast. Garvin deserves rest to heal—not another fight. <<cimg $chars.dataMap.get("captain").img>>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Catgirl Maid` } >> <<set setup.entrycontent = { "en": `<h2 class="entry-header"><<= $chars.dataMap.get("catmaid").name>>, My Catgirl Maid</h2> Today, I met someone who turned my world upside down—again. Her name’s Stellafall, a catgirl from the Repurposed Chambers, and I can’t get her out of my head. I found her in what used to be a dungeon cell, curled up in a corner of that grim stone maze. Back when it was a prison, she was one of the orcs’ kind—well, a monster girl, locked away like so many others. churn. This world’s cruelty keeps hitting me like a slap, but Stellafall? She’s the opposite—a burst of light in the dark. I offered her freedom, no questions asked, and then something crazy happened: I hired her as my maid. Yeah, me, a guy who barely managed office coffee runs, now has a catgirl maid. And she’s not just any maid—she’s a whirlwind of energy. Stellafall’s got this infectious cheer that makes you forget the demons outside our gates. She bounced right up, tail swishing, and said, "I’m Stellafall. They say the hens laid double-yolk eggs the night I was born." I laughed—couldn’t help it. Double-yolk eggs? That’s her story, and she swears it means she’s lucky. Her look is... wow. She’s a perfect blend of feral and beautiful, like a lynx that decided to walk as a woman. Orange hair spills down her shoulders, wild and bright as a sunset, catching every flicker of torchlight. Her eyes? Pure gold, sharp and warm, like they see right through you but choose to smile anyway. She moves with a grace that’s half predator, half dancer—claws tucked away, but you know they’re there. I keep catching myself staring, then feeling like an idiot. She’s a Species all her own, a monster girl who’s more alive than half the nobles I’ve met. Stellafall’s not just a pretty face, though. She’s quick—body and mind. I’ve seen her dart around the chambers, tidying up rubble like it’s a game, chatting with the freed Beastkin to lift their spirits. She’s already made herself at home, claiming a cozy Bedroom with a cracked window she says "lets in the stars." I asked her what she wants, expecting anger or fear after her time locked up. Instead, she grinned and said, "Fish, freedom, and maybe a dance or two!" That’s her—unbreakable, even after everything. Hiring her felt right, but it’s more than that. I think she’s a sign—my lucky break in this mess of a world. The Repurposed Chambers are filling up, and with Stellafall here, it’s starting to feel less like a ghost town. I’m no Lord in shining armor, but if she believes in double-yolk luck, maybe I can believe in her. She’s helping me see this place for what it could be: a home, not a cage. I just hope I don’t let her down. <<cimg $chars.dataMap.get("catmaid").img>>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Goddess` } >> <<set setup.entrycontent = { "en": `<h2 class="entry-header"><<= $chars.dataMap.get("leah").name>>, My Very Questionable Goddess</h2> I’m officially losing it. Meet Leah, a ceramic angel—think dollar-store cherub with a faint glow, parked in my hands like I’m some nutcase collector. Found her in the Lost Temple, and boom, she’s chatting in my head, a voice like radio static only I hear. Says she’s a goddess, names herself Leah, and I’m her Lord. Sure, why not? Now I’m muttering to a figurine while Selena and Raymond side-eye me like I’ve gone feral. Tell them it’s their deity, down to her last spark? Nope, I’d rather eat my Armor. Leah’s no small fry—she made the Planar Rift, a cosmic gash in the temple that could save Hawkridge Barony from demon hordes. Problem? Her stunt blew the Ranch to rubble. Great miracle, Leah. She swears we’ll save the world, but then—brace yourself—she drops this: I need to waltz into the Planar Corridor, play matchmaker for monsters, and, uh, sometimes join in to speed up baby-making. What?! A fertility goddess who’s also our apocalypse fixer? I’m screaming inside—am I nuts, or is she? My Netflix binges didn’t prep me for this plot twist. I’m dodging everyone’s stares harder than demon claws. Leah wants monster girls or others in on this? Spare me! I’m not roping Garvin or Erin into her wild breeding scheme—my dignity’s hanging by a thread. Still, that rift’s our shot against the chaos tearing the fief apart. So, I grit my teeth, clutch my pottery pal, and roll with the monster crew to play savior. Leah’s hiding stuff—her power drain, that explosion, this whole setup smells fishy. I’ll crack her secrets eventually. For now, I’m the guy who’ll save the world, even if it means looking like a total weirdo. One day, they’ll see: I’m the real deal. <<cimg $chars.dataMap.get("leah").img>>` }>>
<<set _condition = () => $times.total >= 5>> <<set setup.entrytitle = { "en": `Griffin Knight` } >> <<set setup.entrycontent = { "en": `<h2 class="entry-header"><<= $chars.dataMap.get("griffinknight").name>>, the Griffin Captain</h2> I met Gregory today, and holy hell, he’s intense. Swooped into the Lost Temple on a griffin—think lion-eagle hybrid, claws like knives. He’s Captain of the 3rd Griffin Squadron, all business in gleaming Armor, voice like a whip: "I’m Gregory. Demons overrun the land; I’m here to scout and collect war taxes." Taxes? My fief’s got 2000 gold, and he wants 100,000. I’m screwed. The guy’s a walking fortress, cold as a spreadsheet, but there’s this... weight to him, like he’s seen too much. Raymond tried to soften him, pleading, "Sir Gregory, for the sake of your bond with the late lord, could you show mercy? He hoped you’d look after his heir." My dad, apparently, trusted him. Gregory didn’t budge much—just gave us a payment plan, not a pass. Cold, sure, but fair? Maybe. Word is, he’s military royalty: dad was a legendary armsmaster, mom the empire’s top alchemist. Garvin swears Gregory smashed Beastfolk wolfriders at Blackstone Gorge with only seven griffins. Seven! I’d trip over a baby one. He rides that griffin across the empire, collecting coin while demons shred everything. Powerful? No question—his presence alone could scare off half my problems. But there’s a chill to him, like he’s all duty, no heart. Still, I caught a flicker when Raymond mentioned my dad—something human. Makes me wonder what he’s not saying. As Lord, I need him on my side, not just my wallet. For now, I’ll pay what I can and keep my eyes on him. This guy’s a force, but nobody’s that untouchable. <<cimg $chars.dataMap.get("griffinknight").img>>` }>>
<<set _condition = () => true>> <<set setup.entrytitle = { "en": `Novice Maid` } >> <<set setup.entrycontent = { "en": `<h2 class="entry-header"><<= $chars.dataMap.get("promaid").name>>, the Novice Maid</h2> I noticed Erin today, our apprentice maid, slipping through the Lost Temple’s halls like a shadow. She’s shy—painfully so—black hair framing a face that ducks every glance. Her maid outfit’s too big, hanging off her like it belongs to someone else, but she works with a focus that’s almost eerie. Words? Barely a whisper. Yet when she tends the wounded, it’s magic—gentle hands, steady as a surgeon. She’s got a gift. Here’s the weird part: Raymond, Garvin, even Selena hover over her like hawks. They’re kind, but cagey. I asked about her past—nothing. Just tight lips and wary looks. There’s a story here, something big, and it’s driving me nuts. As Lord, I’ll figure it out. For now, I’m just glad Erin’s in our Bedroom wing, quietly holding us together. <<cimg $chars.dataMap.get("promaid").img>>` }>>
<<set _condition = () => $times.total > 1>> <<set setup.entrytitle = { "en": `[Day 01]` } >> <<set setup.entrycontent = { "en": `[Day 01] The Starter Pack & Gacha Mechanics 【Event: Awakening & Stellafall the Catgirl】 Subjects: The Ceramic Goddess (Leah), Planar Rift, Stellafall. Mechanics Analysis: UI/Interaction: Leah is a terrible "Guide NPC." Aside from handing out quests, there’s absolutely no documentation. Her so-called "Planar Rift" is clearly just a Gacha pool. Starter Unit (Unit-01: Stellafall): Traits: High DEX, Low STA. A typical Assassin/Scout build. Logic: She showed extreme loyalty immediately upon release (Max Affinity at start?). Her personality type is "Tsundere/Genki," but she displays a terrifying killer instinct in combat. Interaction Test: I touched her when releasing her and felt a static shock. Leah calls it "Soul Binding," but it feels more like Bluetooth pairing. Once paired, I can vaguely sense her location. Daily Summary: Give them a fish, and they'll die for you? Labor costs in this world are ridiculously low.` }>>
<<set _condition = () => $times.total > 2>> <<set _day = 2 >> <<set setup.entrytitle = { "en": `[Day 02]` } >> <<set setup.entrycontent = { "en": `[Day 02] The Slave Market & The "Blind Box" Economy 【Event: The Slaver Caravan's Visit】 Subjects: Slaver, Monster Girls for sale. Market Analysis: Slave trade is legal here, and they have a surprisingly robust pricing system. Pricing Logic: Labor > Aesthetics > Combat. Combat units are cheap because they are volatile and prone to rebellion. Hidden Gems: I eyed some low-level monster girls tagged as "Defective." To the locals, they're scrap. To me, they are top-tier broodmothers. Daily Decision: Spent 500 Gold on three "defects." Raymond the Butler nearly fainted from the cost. Just watch—next week, I'll turn these "scraps" into the territory's greatest assets.` }>>
<<set _condition = () => $times.total > 5>> <<set _day = 5 >> <<set setup.entrytitle = { "en": `[Day 05]` } >> <<set setup.entrycontent = { "en": `[Day 05] The Medieval Sh*tshow & Resource Management 【Event: Inventory Check】 Subjects: Reach Refugees, Moldy Bread, Raymond. Environment Analysis: Don't believe the light novels. The Middle Ages aren't romantic. It stinks here. Hygiene: The refugee camp has no latrines. I had to draw up a blueprint for a septic tank and told Garvin: "Build this, or we die of cholera long before the demons get us." Economic Model: Only few Gold left. With 80+ mouths to feed, if we don't generate revenue, we go bankrupt (and starve) in 10 days. Magic Agriculture: The only upside is "Dawnveil Lake," formed by the explosion. Plants grow 5x faster there. The water is full of high-concentration "Mana Radiation." I've got farmers planting potatoes—hopefully, the crop won't try to eat us. Daily Summary: This isn't an RPG; it's a hardcore survival RTS. Priority one isn't slaying monsters—it's building toilets and farming.` }>>
<<set _condition = () => $times.total > 8>> <<set _day = 8 >> <<set setup.entrytitle = { "en": `[Day 08]` } >> <<set setup.entrycontent = { "en": `[Day 08] Core Gameplay Unlocked: Injection & Speed-Ups 【Event: Acquired Mirael the Succubus】 Subjects: Mirael, The Energy Infuser. Mechanics Analysis: Caught the broken-winged succubus. Leah immediately unlocked the [Energy Infusion] feature. I thought this was just for the H-scenes, and... well, the process is erotic, but functionally, it's a cheat code. Biological Miracle: The Infuser compresses a 30-day gestation period into minutes. Screw thermodynamics. The energy must be deducted directly from Leah's "Divine Power Pool." Output: No babies here—offspring are sent to the Rift (AFK plane) and return as adults. Time inside the Temple domain is clearly non-linear. Mirael's Stats: Despite her broken wings (flight disabled), her [Charm] is off the charts. Even in a weakened state, her presence gives nearby males a "Morale +10%" buff... and a "Sanity -5%" debuff. Side Note: I feel like a sweatshop owner. Mirael seems to enjoy it though (Masochist trait?). As long as we're mass-producing succubus units, ethics can take a backseat. We're surrounded by demons, after all.` }>>
<<set _condition = () => $times.total > 15>> <<set _day = 15 >> <<set setup.entrytitle = { "en": `[Day 15]` } >> <<set setup.entrycontent = { "en": `[Day 15] Intro to Demon Ecology 【Event: Post-Defense Autopsy】 Subjects: Imp Corpses, Mutated Beasts. Biological Analysis: Garvin burned the corpses, but I kept a tissue sample. Under the microscope (I ground the lenses myself), the cells are hyperactive, like cancer. Blood Poison: This isn't a virus. It's corrosive mana residue. It forcibly rewrites DNA, turning humans into zombies. Immunity: Stellafall and Mirael are immune. Their "non-human genes" act as natural antibodies. Conclusion: Humans are weak in this patch. The winning meta is an "All-Monster Girl" or "Hybrid Legion" build. Pure human soldiers are just cannon fodder. It's cruel, but the data doesn't lie.` }>>
<<set _condition = () => $times.total > 17>> <<set _day = 17 >> <<set setup.entrytitle = { "en": `[Day 17]` } >> <<set setup.entrycontent = { "en": `[Day 17] The Hidden Path NPC 【Event: Sariya the Lizard Prophet】 Subjects: Sariya, Ancient Tablet. Trigger: A strange lizard-girl arrived today, claiming to be a prophet. Trait Analysis: [Cold-Blooded]. Her body temp shifts with the environment, making her invisible to thermal detection magic. The perfect stealth unit. Questline: She mentioned an "Ancient Passage." Definitely a main quest clue. There's likely a massive dungeon system beneath the Temple. Social Game: She's not gullible like Stellafall or submissive like Mirael. She has her own agenda. I need to play the "mysterious leader" card perfectly. Daily Summary: Hired her as a "Maid" (actually a Geologist). Her scales are cool to the touch—great for saving on AC in the summer. But I have to watch her. When NPCs get independent AI, variables happen.` }>>
<<set _condition = () => $times.total > 20>> <<set _day = 20 >> <<set setup.entrytitle = { "en": `[Day 20]` } >> <<set setup.entrycontent = { "en": `[Day 20] Religion & The "Signal Tower" Theory 【Event: Fluctuations in the Temple Barrier】 Subjects: The Lost Temple, Leah's Statue. Theory Building: Why can't demons find the entrance? I studied the runes. It's not just magic; it's "Frequency Jamming." The Temple emits a high-frequency mana wave that scrambles their radar. As for the "Goddess of Fertility" doctrine... the "Love and Peace" stuff is total PR fluff. The core doctrine is simple: "Increase Population" (aka Compute Power/Sample Size). Religion is just a management tool here. It keeps the population's SAN values from zeroing out. I'll use that. Am I the Chosen One? Sure, whatever. Kneel before me.` }>>
<<set _condition = () => $times.total > 25>> <<set _day = 25 >> <<set setup.entrytitle = { "en": `[Day 25]` } >> <<set setup.entrycontent = { "en": `[Day 25] Capital & The Black Market 【Event: Hizuki the Fox & The Silver Tail Guild】 Subjects: Hizuki (Nine-tailed? No, Two-tailed), Black Market Ledger. Economy Upgrade: Finally, someone who understands business. Hizuki, a vixen (literally). Business Model: She wants to open a permanent black market here. My territory is the only "Neutral Zone" friendly to monster girls. Negotiation: She tried to lower the tax rate using her looks. Please. I look at succubi all day. All I care about are profit margins. Deal Struck: I provide the venue and security (Gavin's job); she handles the channels and fencing stolen goods. Strategic Value: Eagle's Reach has officially upgraded from "Refugee Camp" to "Trade Hub." I can sell the Temple's excess "produce" to rich idiots outside for supplies. Gold is finally flowing. With money, I can expand faster and buy more blueprints from the System Shop (if it exists).` }>>
<<set _condition = () => $times.total > 30>> <<set _day = 30 >> <<set setup.entrytitle = { "en": `[Day 30]` } >> <<set setup.entrycontent = { "en": `[Day 30] Monthly Review: From Survival to Management 【Event: First Major Defense Successful】 Data Review: Population: 80 -> [Current] (absorbed refugees and bought slaves). Funds: 2,000 -> [Current] (Trade and Quest rewards). Combat Power: One Commander -> A Mixed Squad (Stellafall's Scouts + Mirael's Crowd Control + Sariya's Underground Ops). Reflection: We survived. It's exhausting—babysitting Leah, watching my back with Hizuki, and feeding the vale like livestock. But I've figured out about 70% of this "game's" mechanics. Current Blind Spots: I still view this as a "Hardcore Resource Management Game." Demons are environmental hazards. Monster girls are functional NPCs. I am the Player. I haven't realized what the tablet Sariya found actually implies. Nor have I noticed that the look in Mirael's eyes isn't just submission anymore—it's something heavy, called "love." Whatever. Next month's goal: Deal with Gregory the Tax Collector (Quest delayed 3 months to lower difficulty). Even in an Isekai, the IRS is the toughest Boss.` }>>
<<set _condition = () => false>> <<set setup.entrytitle = { "en": `Note10` } >> <<set setup.entrycontent = { "en": `` }>>
<<set _condition = () => false>> <<set setup.entrytitle = { "en": `Note11` } >> <<set setup.entrycontent = { "en": `` }>>
<div style="font-size: 0.9rem;border:1px solid #8a6c2e;padding: 1rem;max-width:50rem;position:relative;"> <div style="font-size: 1.5rem;border-bottom:1px solid #3d0a0a;padding-bottom:0.5rem;margin-top:0;font-variant: small-caps;"> <i class="fas fa-book-dead"></i> The Pyramid of Civilization & Class Burden </div> <div style="max-height: 80%;overflow-y: auto;padding: 0rem;"> <div> <p style="color: #8a6c2e;padding-bottom: 1rem;"> "A civilization is not a mere horde of gods; it is a tower built upon the backs of the many." <br> — <span style="font-variant: small-caps;">Codex of The Architect</span> </p> <div class="theory-section"> <h3 class="theory-title"><i class="fas fa-layer-group"></i> I. The Edifice of Ascension</h3> <p> Civilization is structured like a grand Ziggurat, constructed <strong>from the bottom up</strong>. The humblest rank (Rank D) forms the bedrock layer. Only when the foundation is solidified can the structure support the weight of the next tier (Rank C). Without a base, there is no spire. </p> </div> <div class="theory-section"> <h3 class="theory-title"><i class="fas fa-compress-arrows-alt"></i> II. The Geometry of Order</h3> <p> Each stratum of society has a "Critical Mass"—a finite capacity for effective contribution. Once a layer is filled to perfection (Standard Unit), that developmental stage is complete. <br><span style="color:#777; font-size:0.9rem;">(Current Standard: 100 Population = 1.0 Level)</span> </p> <p class="theory-warning"> Any excess population within a completed layer cannot force an upward evolution. They merely exist in stagnation—a phenomenon known as <span style="color:#b91c1c;">"Entropy of the Surplus"</span>. </p> </div> <div class="theory-section" style="border-left-color: #d4af37; background: rgba(212, 175, 55, 0.05);"> <h3 class="theory-title" style="color: #d4af37;"><i class="fas fa-weight-hanging"></i> III. The Law of Burden</h3> <p> Gravity pulls the strong downward. If the foundation (D) is missing, the elite (S/A) must descend to fill the void. </p> <p> An <strong>Arch-Demon (Rank S)</strong> forced to till the fields contributes no more to the civilization's complexity than a common peasant. Their divine potential is suppressed by the mundane necessity of survival. This is the burden of leadership—to become the stone when there is no clay. </p> </div> <div class="theory-section"> <h3 class="theory-title"><i class="fas fa-lock"></i> IV. The Ceiling of Ignorance</h3> <p> While the strong may descend, the weak cannot ascend. A multitude of commoners cannot spontaneously generate the wisdom of a sage. Low-rank entities are strictly prohibited from filling empty seats in the higher tiers. The hierarchy is absolute. </p> </div> </div> </div> <p style="font-size:0.9rem; color:#888; font-style:italic;"> "Without the feet, the head cannot reach the sky." </p> </div>
<<widget "ToggleMap">> <<goto "MapsPopup_UI">> <<done>> <<set _toggleTab = "#" + _args[0] + "_map" >> <<removeclass "#temple_map" "active">><<addclass _toggleTab "active">> <</done>> <</widget>>
<<set _doChar = $chars.dataMap.get($training.trainID.do) || $chars.dataMap.get("you") >> <<set _withGirl = $chars.dataMap.get($training.trainID.to) || $chars.dataMap.get("catmaid") >> <div class="train-panel"> <div class="train-panel-right" > <<do tag "interact">><<TrainDataBox _withGirl>><</do>> <div class="interact-container"> <<do tag "geneshow">> <div id="genemore"> <<if def _cssid && _withGirl.genes[_cipi]>> <<GeneFullPanel _withGirl _cipi>> <</if>> </div> <</do>> <div class="interact-info" id="info-content"> <p>Select an action to interact with <<= _withGirl.name>>.</p> </div> </div> </div> <div class="train-panel-left"> <div class="train-panel-left-charcg" style="align-content: center;"> <div class="fullInfo"> <<ShowPortrait _withGirl false false>> </div> </div> <div class="interact-main"> <<do tag "interact">><<TrainStatesBar _withGirl>><</do>> <div class="action-hub"> <div class="ah-header"> <div class="ah-energy-container"> <span class="ah-energy-label">Train Point:</span> <<do tag "interact">> <<LimitActions _doChar.getTPCurrent _doChar.getTPMax>> <</do>> </div> </div> <div class="ah-tabs"> <<set _unlockACTIONS = Object.keys(setup.TRAINING_ACTIONS).filter(key => !["machine", "public", "tentacle", "cock", "special"].includes(key))>> <<for _cii, _cipi range _unlockACTIONS>> <<capture _cii _cipi>> <<if _withGirl.hasPart(_cipi)>> <<link '<div class="ah-tab" @id="_cipi"><<= _cipi>></div>'>> <<removeclass '.ah-tab' 'active'>> <<addclass `"#"+_cipi` 'active'>> <<redo "geneshow">> <<replace '#ah-sub-menu'>> <<ActionSubMenu _cipi _withGirl _doChar>> <</replace>> <</link>> <</if>> <</capture>> <</for>> </div> <div class="ah-body" id="ah-sub-menu"> <div style="color: #6b7280;font-size: 0.9rem;width: 100%;margin-top: 1rem;"> Select a category above to view actions. </div> </div> </div> <div id="action-preview-box"></div> </div></div> <<do tag "interact">><<GenesList _withGirl>><</do>> </div>
<div class="UIframe"> <<include "UI_TopBar">> <div class="ui-main"> <div class="Gcontainer" id="passagecontent"> <<includeplus "Train">> </div> <<include "UI_RightBar">> </div> </div>
<<widget "ActionSubMenu">> <<do tag "interact">> <<set _lockedCount to 0>> <<for _cij, _cisi range Object.values(setup.TRAINING_ACTIONS[_args[0]])>> <<set _fullAction = getFullAction(_cisi.id, _args[1])>> <<set _actionState = checkActionState(_fullAction, _args[1], _args[2])>> <<capture _cij _cisi _fullAction _args[1] _args[2] _args[0] _actionState>> <<if _actionState.status === "LOCKED_HIDDEN">> <<set _lockedCount += 1>> <<else>> <<set _btnClass = "ah-action-btn ">> <<set _subText = "">> <<switch _actionState.status>> <<case "AVAILABLE">> <<set _btnClass += "available">> <<set _subText = `Sta ${_fullAction.stamina} | Men ${_fullAction.mental}`>> <<case "LOCKED_RESOURCE">> <<set _btnClass += "locked-resource">> <<set _subText = `<span style="color: #f87171;">No Energy</span>`>> <<case "LOCKED_TAME">> <<set _btnClass += "locked-stat">> <<set _subText = `<span style="color: #fbbf24;">Req Tame ${_actionState.reqTame}</span>`>> <<case "LOCKED_TYPE">> <<set _btnClass += "locked-type">> <<set _subText = `<span style="color: #9ca3af;">Type Mismatch</span>`>> <<case "LOCKED_STATE">> <<set _btnClass += "locked-stat">> <<set _subText = `<span style="color: #a78bfa;">Req States</span>`>> <</switch>> <<link '<div @class="_btnClass" @id="_cisi.name"><div class="ah-action-name"><<= _cisi.name>></div><div class="ah-action-sub"><<= _subText>></div></div>'>> <<run TrainPreviewUI.showPreview(_cisi.id, _args[1], _args[2])>> <<replace "#action-preview-box">> <<ActionConfirmPanel _fullAction _args[1] _args[2] _args[0] _actionState>> <</replace>> <</link>> <</if>> <</capture>> <</for>> <<if _lockedCount > 0>> <<link '<div class="ah-action-btn locked-type" style="justify-content: center;"><div><span class="ah-action-name" style="font-size:1.5rem;">🔒</span> x <<= _lockedCount>></div></div>'>> <<run Msg.flash('Still ' + _lockedCount + ' action completely locked/hidden!') >> <</link>> <</if>> <</do>> <</widget>> <<widget "ActionConfirmPanel">> <<set _action = _args[0]>> <<set _trainee = _args[1]>> <<set _trainer = _args[2]>> <<set _part = _args[3]>> <<set _state = _args[4]>> <div class="preview-panel"> <h3 style="color: #fca5a5; margin-top:0; margin-bottom: 0.5rem;"><<= _action.name>></h3> <p style="font-size: 0.9rem; font-style: italic; margin-top: 0;"><<= _action.desc>></p> <div class="preview-stats-grid"> <div class="preview-stat-item"> Stamina: <span class="cost"><<= _action.stamina>></span> </div> <div class="preview-stat-item"> Mental: <span class="cost"><<= _action.mental>></span> </div> <div class="preview-stat-item"> Heat: <span class="gain">+<<= _action.heat>></span> </div> <div class="preview-stat-item"> Tame: <span class="gain">+<<= _action.tame>></span> </div> <div class="preview-stat-item"> Loyal: <span class="gain">+<<= _action.loyal>></span> </div> </div> <<if _action.expSources>> <<if Object.keys(_action.expSources.target).length > 0>> <div class="preview-exp-section"> <div class="preview-exp-title"><<= _trainee.name>> (Receiver) Genes EXP:</div> <div class="preview-exp-grid"> <<for _partName, _weight range _action.expSources.target>> <div class="preview-stat-item"> <span class="text-capitalize"><<= _partName>>:</span> <span class="gain">+<<= Math.floor(_action.experience * _weight)>></span> </div> <</for>> </div> </div> <</if>> <<if Object.keys(_action.expSources.giver).length > 0>> <div class="preview-exp-section"> <div class="preview-exp-title"><<= _trainer.name>> (Giver) Genes EXP:</div> <div class="preview-exp-grid"> <<for _partName, _weight range _action.expSources.giver>> <div class="preview-stat-item"> <span class="text-capitalize"><<= _partName>>:</span> <span class="gain">+<<= Math.floor(_action.experience * _weight)>></span> </div> <</for>> </div> </div> <</if>> <</if>> <<if (_action.addState && _action.addState.length > 0) || (_action.removeState && _action.removeState.length > 0)>> <div class="preview-state-changes"> <<if _action.addState && _action.addState.length > 0>> <div class="psc-group"> <span class="psc-title">Applies State:</span> <div style="display:flex; gap:0.4rem; flex-wrap:wrap;"> <<for _s range _action.addState>> <<set _sDef = setup.trainStateData[_s]>> <<if _sDef>> <span class="psc-badge add" @title="_sDef.desc"> <<= _sDef.icon>> <<= _sDef.name>> </span> <</if>> <</for>> </div> </div> <</if>> <<if _action.removeState && _action.removeState.length > 0>> <div class="psc-group"> <span class="psc-title">Removes State:</span> <div style="display:flex; gap:0.4rem; flex-wrap:wrap;"> <<for _r range _action.removeState>> <<if _r.startsWith("#")>> <span class="psc-badge remove" title="Clears states with this tag"> 🧹 Tag: <<= _r.slice(1)>> </span> <<else>> <<set _rDef = setup.trainStateData[_r]>> <<if _rDef>> <span class="psc-badge remove" @title="_rDef.desc"> <<= _rDef.icon>> <<= _rDef.name>> </span> <</if>> <</if>> <</for>> </div> </div> <</if>> </div> <</if>> <<if _state.status !== "AVAILABLE">> <div class="preview-lock-warning"> ⚠️ Requires: <<= _state.message>> </div> <</if>> <div class="preview-actions"> <<link '<div class="btn-cancel">Cancel</div>'>> <<run TrainPreviewUI.clearPreview()>> <<replace "#action-preview-box">><</replace>> <</link>> <<if _state.status === "AVAILABLE">> <<link '<div class="btn-confirm">Confirm</div>'>> <<run TrainPreviewUI.clearPreview()>> <<replace "#action-preview-box">><</replace>> <<TrainCG _action _trainee.name>> <<run trainAction(_trainer, _trainee, _action.id)>> <<run EventBus.emit("action:performed", { id: _trainee.rankId, action: _part, value: 1 })>> <<run EventBus.emit("action:record", { traineeId: _trainee.rankId, category: _part, actionId: _action.id, value: 1 })>> <<timed 1s>><<run _trainee.markDirty()>><<redo "interact">><</timed>> <</link>> <<else>> <div class="btn-disabled">Confirm</div> <</if>> </div> </div> <</widget>> <<widget "LimitStars">> <div class="limit-stars"> <<for _i range _args[1]>> <<if _i < _args[0]>> <div class="limit-star active"></div> <<else>> <div class="limit-star"></div> <</if>> <</for>> </div> <</widget>> <<widget "LimitHearts">> <div class="limit-hearts"> <<for _i range _args[1]>> <<if _i < _args[0]>> <div class="limit-heart">❤️</div> <<else>> <div class="limit-heart">🖤</div> <</if>> <</for>> </div> <</widget>> <<widget "LimitActions">> <div class="ah-energy-pips"> <<set _current = _args[0]>> <<set _max = _args[1]>> <<for _i to 0; _i < _max; _i++>> <<if _i < _current>> <div class="ah-pip active"></div> <<else>> <div class="ah-pip"></div> <</if>> <</for>> </div> <</widget>> <<widget "UnlockDNA">> <div class="dna-container" id="dnaHelix"></div> <<done>> <<script>> setup.showDNA(); <</script>><</done>> <</widget>> <<widget "FlashShow">> <<timed 0.1s>> <<script>> const overlay = document.getElementById('flashOverlay'); overlay.style.animationIterationCount = State.temporary.args[0] ? State.temporary.args[0] : 2; overlay.style.display = 'block'; <</script>><</timed>> <</widget>> <<widget OpUpgrade>> <<run Dialog.create("", "infoPop")>> <<run Dialog.wiki(`<<UnlockDNA>>🔓<<= langBank(_args[0])>> Potential Tier Has Upgraded!`)>> <<run Dialog.open()>> <</widget>> <<widget "TrainStatesBar">> <<set _char = _args[0]>> <div class="train-states-bar"> <<if _char.train.states && Object.keys(_char.train.states).length > 0>> <<for _stateId, _stateData range _char.train.states>> <<set _def = setup.trainStateData[_stateId]>> <<if _def>> <div class="ts-item" @title="_def.desc"> <span class="ts-icon"><<= _def.icon>></span> <span class="ts-name"><<= _def.name>></span> <<if _stateData.duration < 90>> <span class="ts-duration"><<= _stateData.duration>>t</span> <</if>> </div> <</if>> <</for>> <<else>> <span class="ts-empty">No active states.</span> <</if>> </div> <</widget>>
<<widget TrainDataBox>> <<set _withGirl = _args[0] >> <<done>> <<script>> const attributes = { stamina: {}, mental: {}, heat: {} }; const animationRequests = {}; const TOTAL_STEPS = 50; const STEP_DURATION = 10; function initAttributes() { const char = State.temporary.withGirl; for (const attrName in attributes) { attributes[attrName].valueEl = document.getElementById(attrName+'-value'); attributes[attrName].barEl = document.getElementById(attrName+'-bar'); if (attrName ==="stamina") { attributes[attrName].max = char.getStaminaDaily; } else if (attrName ==="mental") { attributes[attrName].max = Math.trunc(char.getMentalMax); } else if (attrName ==="heat") { attributes[attrName].max = 100; } else { }; }; }; window.animateAttribute = function(attrName, changeValue) { const char = State.temporary.withGirl; if (attrName ==="stamina") { attributes[attrName].current = char.getStaminaCurrent; } else if (attrName ==="mental") { attributes[attrName].current = char.getMentalCurrent; } else if (attrName ==="heat") { attributes[attrName].current = char.train.heat; } else { attributes[attrName].current = setup.geneTier.currentExp(char[attrName].op) * 10; attributes[attrName].max = setup.geneTier.requiredExp(char[attrName].op) * 10; }; const targetValue = attributes[attrName].current + changeValue; if (changeValue === 0 || targetValue === attributes[attrName].current) { return; } let effectiveTarget = targetValue; if (attrName !="stamina") { if (targetValue > attributes[attrName].max) { effectiveTarget = attributes[attrName].max; } else if (targetValue < 0) { effectiveTarget = 0; } }; const startValue = attributes[attrName].current; const change = effectiveTarget - startValue; const stepValue = change / TOTAL_STEPS; let currentStep = 0; let startTime = null; if (animationRequests[attrName]) { cancelAnimationFrame(animationRequests[attrName]); animationRequests[attrName] = null; }; function step(timestamp) { if (!startTime) startTime = timestamp; const elapsed = timestamp - startTime; const progress = Math.min(elapsed / (TOTAL_STEPS * STEP_DURATION), 1); currentStep = Math.round(progress * TOTAL_STEPS); let current = Math.round(startValue + stepValue * currentStep); current = Math.min(Math.max(current, 0), attributes[attrName].max); const percent = (current / attributes[attrName].max) * 100; attributes[attrName].valueEl.textContent = current + ' / ' + attributes[attrName].max; attributes[attrName].barEl.style.width = percent + '%'; if (currentStep < TOTAL_STEPS) { animationRequests[attrName] = requestAnimationFrame(step); } else { attributes[attrName].current = effectiveTarget; animationRequests[attrName] = null; if (targetValue >= attributes[attrName].max) { attributes[attrName].current = 0; attributes[attrName].valueEl.textContent = '0 / ' + attributes[attrName].max; attributes[attrName].barEl.style.width = '0%'; setTimeout(() => { animateAttribute(attrName, targetValue - attributes[attrName].max); }, 200); } } } animationRequests[attrName] = requestAnimationFrame(step); }; initAttributes(); <</script>> <</done>> <div class="train-container"> <div class="train-left-column"> <div class="train-title"><<= _withGirl.name>><<if isVirgin(_withGirl)>> [🌸]<</if>></div> <<if _withGirl.id !== "you">><div class="train-sub-title"><span style="font-size: 1.25rem;"><<LimitHearts _withGirl.bond.vl _withGirl.bond.max>></span></div><</if>> <div class="train-stat-row" id="Stamina" style="width: 352px; justify-content: flex-start;"> <span class="train-stat-label train-stat-solo">Stamina</span> <div class="train-stat-bar" @style="`height: 24px; flex: none;width: ${Math.min(268, _withGirl.getStaminaDaily)}px;`"> <div class="train-stat-preview" id="stamina-preview" style="width: 0%;"></div> <<set _attoppro = `width: ${Math.trunc(_withGirl.getStaminaCurrent / _withGirl.getStaminaDaily * 100)}%;`>> <div class="train-stat-fill" id="stamina-bar" @style="_attoppro"></div> <span class="train-stat-value" id="stamina-value"><<= _withGirl.getStaminaCurrent>> / <<= _withGirl.getStaminaDaily>></span> </div> <span class="train-stat-change">+0%</span> </div> <div class="train-stat-row" style="width: 352px; justify-content: flex-start;"> <span class="train-stat-label train-stat-solo">Mental</span> <div class="train-stat-bar" @style="`height: 24px; flex: none;width: ${Math.min(268, _withGirl.getMentalMax)}px;`"> <div class="train-stat-preview" id="mental-preview" style="width: 0%;"></div> <<set _attoppro = `width: ${Math.trunc(_withGirl.getMentalCurrent / _withGirl.getMentalMax * 100)}%;`>> <div class="train-stat-fill train-blue" id="mental-bar" @style="_attoppro"></div> <span class="train-stat-value" id="mental-value"><<= _withGirl.getMentalCurrent>> / <<= _withGirl.getMentalMax>></span> </div> <span class="train-stat-change">+0%</span> </div> <div class="train-stat-row"> <span class="train-stat-label train-stat-solo">Loyal</span> <div class="train-stat-bar"> <div class="train-stat-preview" id="loyal-preview" style="width: 0%;"></div> <<set _attoppro = `width: ${(_withGirl.train.loyal).toFixed(0)}%;`>> <div class="train-stat-fill train-pink1" id="loyal-bar" @style="_attoppro"></div> <span class="train-stat-value" id="loyal-bar"><<= setup.loyalTier.tag(_withGirl.train.loyal)>><<= " | " + _withGirl.train.loyal>></span> </div> <span class="train-stat-change">+0%</span> </div> <div class="train-stat-row"> <span class="train-stat-label train-stat-solo">Tame</span> <div class="train-stat-bar"> <div class="train-stat-preview" id="tame-preview" style="width: 0%;"></div> <<set _attoppro = `width: ${(_withGirl.train.tame).toFixed(0)}%;`>> <div class="train-stat-fill train-pink2" id="tame-bar" @style="_attoppro"></div> <span class="train-stat-value" id="tame-bar"><<= setup.tameTier.tag(_withGirl.train.tame)>><<= " | " + _withGirl.train.tame>></span> </div> <span class="train-stat-change">+0%</span> </div> <div class="train-stat-row"> <span class="train-stat-label train-stat-solo">Heat</span> <div class="train-stat-bar"> <div class="train-stat-preview" id="heat-preview" style="width: 0%;"></div> <<set _attoppro = `width: ${(_withGirl.train.heat).toFixed(0)}%;`>> <div class="train-stat-fill train-white" id="heat-bar" @style="_attoppro"></div> <span class="train-stat-value" id="heat-value"><<= _withGirl.train.heat>> / 100</span> </div> <span class="train-stat-change">+0%</span> </div> <div class="train-databox"><div>Grade Progress</div><div> <div class="train-stat-opstars-title"><span>Growth Grade / Limit</span></div></div></div> <<for _attop, _attopRaw range setup.coreStatData>> <<set _attopname = _attopRaw.shortName >> <<capture _attop _attopname>> <<set _nextop = setup.geneTier.upgrade(_withGirl[_attop].op);>> <<set _attoppro = `width: ${((_withGirl[_attop].op) / setup.geneTier.upgrade(_withGirl[_attop].op) * 100).toFixed(0)}%;`>> <div class="train-databox"> <div class="train-stat-row"> <span class="train-stat-label"><<= _attopname>></span> <div class="train-stat-bar"> <div class="train-stat-fill" @id="_attop+'-bar'" @style="_attoppro"></div> <span class="train-stat-value" @id="_attop+'-value'"><<= (_withGirl[_attop].op).toFixed(1)>> / <<= setup.geneTier.upgrade(_withGirl[_attop].op).toFixed(1)>></span> </div> <span class="train-stat-change">+0%</span> </div> <div><<do tag "interact">> <div class="train-stat-opstars"><span><<= _withGirl.getRank(_attop)>></span> <span><<LimitStars _withGirl.atttostars(_attop) _withGirl.getattcap(_attop)>></span></div> <</do>></div> </div> <</capture>> <</for>> </div> <<do tag "interact">> <div class="train-right-column"> <div class="train-footer-stats"> <div>Sex Exp</div> <div>Oral: <<= _withGirl.genes.mouth.status.times || 0>></div> <div>Breast: <<= _withGirl.genes.breast.status.times || 0>></div> <div>Hands: <<= _withGirl.genes.hand.status.times || 0>></div> <<if _withGirl.genes?.pussy>><div>Pussy: <<= _withGirl.genes.pussy.status.times || 0>></div><</if>> <<if _withGirl.genes?.cock>><div>Cock: <<= _withGirl.genes.cock.status.times || 0>></div><</if>> <div>Anal: <<= _withGirl.genes.ass.status.times || 0>></div> <div> </div> </div> <div class="train-footer-stats"> <div>Offspring: <<= _withGirl.offspring.length>></div> <div>Summon: <<= _withGirl.summon>></div> <div>Harvest: <br>-<<= getiteminfo(_withGirl.harvest.id,'name')>><br> </div> <div>Traits: <br><<TraitsList _withGirl>></div> </div> <div class="train-traits"></div> <<TrainStatesBar _withGirl>> </div> <</do>> </div> <</widget>>
<<widget GenesList>> <div class="monster-status-panel"> <div class="helptip"><<link "❓">><<SimpleUI "Gene_Help">><</link>></div> <div class="title-fs125"><span class="text-capitalize"><<= _args[0].name>></span><<if isVirgin(_args[0])>> [🌸]<</if>></div> <div class="grid-3col"> <<for _stat range setup.coreStats>> <<set _val to _args[0][_stat].op>> <div @class='"solt-center flex-col-center "+ _args[0].getRank(_stat)'> <div class="item-name"><<= _stat.toUpperCase() >></div> <div class="item-value"><<=_args[0].getRank(_stat)>><<= " ("+ _val + ")">></div> </div> <</for>> </div> <div class="genelist-cards"> <<for _onegene range setup.geneUtils.order(_args[0].genes)>> <<set _gene = _onegene.data >> <<capture _gene _args[0].gender>> <<if _onegene.level !== 2>> <<link "<<SimpleGene _gene _args[0].gender>>">><<replace "#genemore">><<GeneFullPanel _args[0] _gene.part>><</replace>><</link>> <</if>> <</capture>> <</for>> </div> </div> <</widget>> <<widget SimpleGene>> <<set _gene = _args[0] >> <<if !["cloth", "body"].includes(_gene.part)>> <<set _cardclass = `solt-card ${_onegene.level == 2 ? "level2" : ""}` >> <div @class='`solt-card ${_onegene.level == 2 ? "level2" : ""} ${_args[1] ?? "girl"}`'> <div class="title-fs100"> <<= _gene.part.toUpperCase()>> <<if _gene.part == "pussy" && _gene.status.times === 0>>[Virgin]<</if>> </div> <div class="text-fs080"> <<=_gene.type>> </div> <div class="text-fs080"> <<if setup.genedata?.[_gene.part]?.isCalc>> <<set _mods to setup.geneUtils.modifiers(_gene)>> <<set _modText to []>> <<for _k, _v range _mods>> <<if Math.abs(_v) > 0.01>> <<set _modText.push(`${_k.toUpperCase()}${_v>0?"+":""}${_v.toFixed(1)}`)>> <</if>> <</for>> <<=_modText.join(" ")>> <</if>> </div> <div class="text-fs075"> <<if _gene.status?.sizeModifier !== undefined>> Modifier: <<= (setup.geneUtils.factor(_gene) * 100).toFixed(0) + "%">> | <</if>> Origin: <span class="text-capitalize"><<=_gene.race>></span> | Rank: <<=_gene.rank>> <<if _gene.isTraining>><br><span>Times: <<=_gene.status.times>> | First: <<=_gene.status.firstPartner>></span><</if>> </div> </div> <</if>> <</widget>>
<<widget "ShowSummonCG">> <div id="paircg-app-root" class='paircg-app-root'></div> <<done>> <<set _cgimages = [ setup.ImagePath + _args[0], ]; >> <<set _cgcolors = {}>> <<set _pet0 = $summon.slots[0].race>> <<if $summon.slots[0].type == "unique">><<set _pet0 = $summon.slots[0].id>><</if>> <<set _pet1 = $summon.slots[1].race>> <<if $summon.slots[1].type == "unique">><<set _pet1 = $summon.slots[1].id>><</if>> <<set _isSwapped = (_pet0.toLowerCase() !== [_pet0.toLowerCase(), _pet1.toLowerCase()].sort()[0]) >> <<if _isSwapped>> <<set _monA = $summon.slots[1]>> <<set _monB = $summon.slots[0]>> <<else>> <<set _monA = $summon.slots[0]>> <<set _monB = $summon.slots[1]>> <</if>> <<if["human", "allureborn"].includes(_monA.race)>> <<set _cgcolors.tintR = _monA.atlas.colors.skin >> <<else>> <<set _cgcolors.tintR = _monA.atlas.colors.fur >> <</if>> <<if["human", "allureborn"].includes(_monB.race)>> <<set _cgcolors.tintG = _monB.atlas.colors.skin >> <<else>> <<set _cgcolors.tintG = _monB.atlas.colors.fur >> <</if>> <<set _cgcolors.tintB = null >> <<set _finishAction = '<<DualSummon $summon.slots[0] $summon.slots[1]>><<redo "topbar selected">>';>> <<run TintViewer.init("paircg-app-root", _cgimages, _cgcolors, _finishAction);>> <</done>> <</widget>>